Why does Upserting with HotChocolate no longer work - graphql

I recently upgraded HotChocolate.AspNetCore from 11.0.8 -> 11.3.8 and started running into an issue. As a simple example, say I have a Book and a Page entity, with records defined like so:
public record AddBookInput(string Title, AddPageInput[] pages);
public record AddPageInput(string Text)
public record UpdateBookInput(Guid Id, string Title, UpdatePageInput[] pages);
public record UpdatePageInput(Guid Id, string Text)
Previously, when updating a Book, I could pass both existing and new Page entities, and the new Pages would be added (even though the input specifies an UpdatePageInput):
{
"id": "123",
"title": "Updated Title",
"pages": [
{ id: "456", text: "Updated Text" },
{ text: "New Page" }
]
}
But now, this results in an error on the "pages" variable that "The field 'id' is required and must be set".
Does anyone know the underlying change behind this, or if there's a workaround besides defining separate "pages" parameters for add and update?

Related

Spring boot - Customize order of properties in response

I have a base model class with createdAt and updatedAt properties, and a user model extending that class, when spring returns the json, it puts the createdAt / updatedAt properties first, which isn't breaking anything, just super weird to look at, so I end up with something like this:
{
"createdAt": "2022-07-14T06:31:04.933027-04:00",
"updatedAt": "2022-07-14T06:31:04.933027-04:00",
"id": 1,
"email": "foo#bar.com",
"firstName": "Foo",
"lastName": "Bar"
}
Ideally, I'd like it to look like this if possible / be able to customize the order:
{
"id": 1,
"email": "foo#bar.com",
"firstName": "Foo",
"lastName": "Bar",
"createdAt": "2022-07-14T06:31:04.933027-04:00",
"updatedAt": "2022-07-14T06:31:04.933027-04:00"
}
I was facing the exactly same issue just as you do - I wanted the createdDate/updateDate from base class and only want them shown at the bottom.
annotation JsonPropertyOrder is the answer.
option 1) specify each property name in desired order in this annotation(put on class level), i.e.:
#JsonPropertyOrder(value = {"id", "email", "firstName", "lastName", "createdAt", "updatedAt"})
public class SubClass {
}
however, it's not good practice to hardcode property names as plain text, which lead to unexpected order in case you renamed an property but not updated the hardcoded name to be the same.
option 2) as this answer says https://stackoverflow.com/a/70165014/6228210, use the annotation JsonProperty to customize the order of each property. I verified it works very well.
see the sample(copied from that answer):
class animal {
#JsonProperty(index=2)
int p1;
#JsonProperty(index=3)
int p2;
}
class cat extends animal{
#JsonProperty(index=1)
int p3;
}
I personally believe this is better option.
to improve it a bit, I would suggest specifying the index this way, like 5, 10, 15, 20... for sub class and a big number 99 and 100 for base class properties, which makes it easy for you to insert new properties in the future and won't mess up the orders.
Normally the order should be the same as the model. If you want to change the order, do it in the model.

OData v4 Web API 2.2 deep level expand not working

The situation
I'm trying to expand "Item" to three levels:
Item.Product.Model.Type
So I call this nested query options url:
http://xxx/api/Items?$expand=Product($expand=Model($expand=Type))
I Get a warning that the max depth of 2 has been reached so I set the suggested MaxExpansionDepth attribute to 3. But then, the "Type" property is not returned! This is covered by this SO question
Then I look at the official OData V4 standard and it says I should use slashes in the expand option, like so:
http://xxx/api/Items?$expand=Product/Model/Type
But that gives me an error telling:
The query specified in the URI is not valid. Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties.
Which this SO answer covers but the answer is contradictory to the official OData doc. What does that even mean anyway.
The question
What is the official, standard and working way of using the $expand query option for deep levels with OData v4 and Web API 2.2
After working with Jerther in chat, we narrowed the problem down to the expanded properties not being marked as contained navigations. As a result, the OData framework was removing them since they did not have corresponding entity sets defined. Updating the model to specifically declare the containment appears to have solved the problem.
Containment can be specified in a couple of ways, depending on what model builder is being used. In the case of the ODataConventionModelBuilder you can add the System.Web.OData.Builder.ContainedAttribute to the property in question, while for the ODataModelBuilder you can use the ContainsMany<T> method on the EntityTypeConfiguration instance for the containing class.
Also, at the moment, a cascaded expand will stop where a complex type contains an Entity type.
UPDATE:
Defining all types in the chain as EntitySet works.
builder.EntitySet<Item>("Items");
builder.EntitySet<Product>("Products");
builder.EntitySet<Model>("Models");
builder.EntitySet<Type>("Types");
It seems defining them as EntityType isn't sufficient.
see here: https://github.com/OData/WebApi/issues/226
Original Answer
I tried reproing your situation and couldn't. Is it possible that "Types" isn't being set in your action? Here was my little repro
public class ItemsController : ODataController
{
[HttpGet]
[EnableQuery(MaxExpansionDepth = 10)]
[ODataRoute("/Items")]
public IHttpActionResult GetItems()
{
return this.Ok(CreateItem());
}
private Item CreateItem()
{
return new Item
{
Id = 1,
Products = new Product[]
{
new Product
{
Id = 2,
Models = new Model[]
{
new Model
{
Id = 3,
Types = new MyType[]
{
new MyType
{
Id = 4,
},
},
},
},
},
},
};
}
}
Which when called with /Items?$expand=Products($expand=Models($expand=Types)) resulted in the following:
{
"#odata.context": "http://localhost:9001/$metadata#Items/$entity",
"Id": 1,
"Products#odata.context": "http://localhost:9001/$metadata#Items(1)/Products",
"Products": [{
"Id": 2,
"Models#odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models",
"Models": [{
"Id": 3,
"Types#odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models(3)/Types",
"Types": [{
"Id": 4
}]
}]
}]
}

How can I partially update an embedded document using Spring Data MongoDB's MongoTemplate?

Please can some one tell me how can I update a record in an embedded document? Here is my document structure:
{
"_id":"1000",
"event_name":"Some name",
"tracks":
[
{
"id" : "100"
"title" : "Test title",
},
{
"id" : "101",
"title" : "Test title 2",
}
]
}
I want to update title of track having id 101. As related to my question as there.
I am able to solve my problem by using the following piece of code
MongoConverter converter = mongoTemplate.getConverter();
DBObject newTrackRec = (DBObject)converter.convertToMongoType(track);
Query query = Query.query(Criteria.where("_id").is(track.getEventId()).and("tracks._id").is(track.getId()));
Update update = new Update().set("tracks.$", newTrackRec);
mongoTemplate.updateFirst(query, update, Event.class);
You basically follow the instructions of the MongoDB reference documentation and translate that into the Spring Data MongoDB query and update APIs:
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
Track track = …;
String title = …;
Query query = query(
where("id").is(track.getEventId()).
and("tracks.id").is(track.getId()));
Update update = new Update().set("tracks.$.title", title);
mongoTemplate.updateFirst(query, update, Event.class);

Value cannot be null. Parameter name: source in MVC listbox

I have found plenty of posts about this, but none of them are solving my issue. My code right now:
#Html.ListBox("SelectedNewsletter", Model.Newsletters)
and
public MultiSelectList Newsletters
{
get
{
return new MultiSelectList(
new[]
{
// TODO: Fetch from your repository
new { Id = 1, Name = "item 1" },
new { Id = 2, Name = "item 2" },
new { Id = 3, Name = "item 3" },
},
"Id",
"Name"
);
// return new MultiSelectList(PromoNewsletter.All, "IdString", "Display");
}
}
As far as I can see, it's all hard coded now, and it still gives the same error. I want to do a ListboxFor, but I am trying to just get a listbox to work. I have replaced my int id with a string representation, based on advice I found elsewhere, but now I don't see what else I can do. It just plain is not working, even with all hard coded values and not binding to a property on my ViewModel. Where am I going wrong ?
The error is occurring because you have a property in the Model or ViewData/ViewBag with the name SelectedNewsletter.
Give a different name for the ListBox if you can't rename that property.
UPDATE:
After little more experimenting I figured out that the problem is you may be setting an integer value to the SelectedNewsletter that is somewhere in the ViewData/ViewBag or Model.
You can set the values that has to be selected in the ListBox as a string or array of strings to the SelectedNewsletter.
i.e SelectedNewsletter = "1";
or
SelectedNewsletter = new[] { "1", "3" };
You can also use strongly typed helper to make things easy,
#Html.ListBoxFor(m => m.SelectedNewsletter, Model.NewsLetters);

MVC 1.0 DropDownList static data select not working

I'm new to MVC and C#. I'm trying to get a static list to work with a DropDownList control such that the selected value rendered is set by the current Model value from the DB.
In the controller, I have:
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, donor.Gender);
In the view:
Gender:<%=Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewData["GenderList"]) %>
In the debugger, donor.Gender is "Male", but "Female" gets rendered in the view.
I've read a number of posts related to select, but I've not found one that applies to a static list (e.g., where there's no "value" and "name" to play with). I'm probably doing something stupid...
This may sound like a stupid question but is donor.Gender a string value, and does it match case with the hard-coded values you've used EG 'Male'?
The reason I ask is because this;
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, "Male");
works a treat, but this;
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, "male");
returns your result
Thanks to Si, here's the final solution I came up with:
In the controller, this:
ViewData["GenderList"] = repo.GetGenders(donor.Gender);
In the DonorRepository, this:
public SelectList GetGenders(string selected) {
SelectList genders = new SelectList(new[] { "Female ", "Male ", "Unknown" }, (selected == null ? null : selected.ToString().PadRight(7).ToCharArray()));
return (genders);
}
Then in the View, just this:
<%= Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewData["GenderList"], "--Select--")%>
NOTE: PadRight(7) equals the Donor.Gender DB specification of Char(7). Also note the SelectList constant space padding of "1234567" for each selectlistitem.

Resources