Hi,
Updating data in a grid bound to an OData V4 datasource fails if the data has boolean type columns. In my example, a grid is bound to OData controller, and an update to [Notes] is generating the JSON below in the PATCH request:
{
"Id": 719665,
"DisplayToClient": true,
"Notes": "Bought on Strat 10, late, but looks good. Set sell 5.11abcde",
"GlClientAuto": true,
"GlAdminAuto": true,
"InstrumentE": {
"PenaltyPreMaturity": false
}
}
In this example, only the [Notes] field was actually updated, but DataGrid & SfDataManager generated PATCH update including ALL Boolean fields, even though they were not touched. In fact, they are not even bound to columns in the Grid.
The failure occurs either in inline edit mode, and Dialog mode. The fields added to the update list above are all the Boolean fields, so that would appear to be the cause/issue.
I am aware that there is already a related bug logged, to do with setting Booleans to false:
But this is not this same thing. The bug I am experiencing is worse, as NO updates to the data are possible in this case, as the bug is extending through to the related table InstrumentE above, which is not updatable.
Changing the SFDataManager update mode to POST is also not a solution, as this attempts to update all data, including the expanded InstrumentE, which is not OK here.
Please advise.
Hi Phil,
We
have considered it as a bug and logged an issue “Update Fail
for ODataV4Adapter on Grid (with Boolean)” for the same. Thank you for taking the time to
report this issue and helping us to improve our product. At Syncfusion, we are
committed to fix all validated defects (subject to technological feasibility
and Product Development Life Cycle) and this fix will be included in any of our upcoming patch
release.
You can now track the current status of your request, review the proposed resolution timeline, and contact us for any further inquiries through this link.
https://www.syncfusion.com/feedback/40910/update-fail-for-odatav4adapter-on-grid-with-boolean
Disclaimer: “Inclusion of this solution in the weekly release may change due to other factors including but not limited to QA checks and works reprioritization”
Until then we appreciate your patience.
Regards,
Sarvesh
Hi Phil,
Thanks for the patience
We are glad to announce that, we have included the fix for the
issue “Update
Fail for ODataV4Adapter on Grid (with Boolean)”
in our volume 1 release (21.1.35).
So
please upgrade to our latest version of Syncfusion NuGet package to resolve the
reported issue. Please find the NuGet package for latest fixes and features
from below.
NuGet : https://www.nuget.org/packages/Syncfusion.Blazor.Grid
We thank you for your support and appreciate your patience in waiting for this release. Please get in touch with us if you would require any further assistance.
Regards,
Sarvesh
Hi Sarvesh,
Sorry, problem NOT fixed.
If you check the thread, I noted that there was also a reported bug 34335, which maybe you have done something about, but my point of noting it was that this was a different bug.
The errant behaviour as I have described above is still present in 21.1.35. Unchanged.
Regards,
Phil
Hi Phil,
Sorry for the delay and inconvenience caused.
We request you to define the available Boolean type properties as nullable
Boolean to resolve the reported issue. Kindly refer the attached code snippet
for your reference
public class Order { [Key] public Guid Id { get; set; } . . . . .. public bool? IsOrderFinalized { get; set; } public bool? DisplayToClient { get; set; } public bool? GlClientAuto { get; set; } public bool? GlAdminAuto { get; set; } } |
Please get back to us if you have any further query.
Regards,
Sarvesh
Thanks,
That does work, I'd suggest as a workaround. These fields are not actually nullable.
Can this still be fixed so that this workaround is not required, or is it not being considered as a bug?
Please let me know.
Regards,
Phil
Hi Phil,
Sorry for the delay in getting back to you.
Query: “That does work, I'd suggest as a workaround. These fields are not actually nullable. Can this still be fixed so that this workaround is not required, or is it not being considered as a bug?”
We have analyzed the reported query at our end and we would like to inform you that this (solution provided) is possible solution to overcome this issue, not an workaround.
SfDataManager component is generic standalone component used to fetch data source from service and perform Data / CRUD operation based on the query passed to it from Grid or other components. It operates based on the TValue defined in the dependent component. Also we will not be able to access the Columns defined in the SfDataManager. So we will not be able to identify what are all the columns defined in Grid component.
During CRUD operation, we will render the Blazor EditForm using the object type as EditContext and on saving the edit context value will be sent to SfDataManager. At SfDataManager level we will not be able to differentiate which ever properties are changed in edit form, which ever properties are not affected. Because GridColumn details cannot be access at the SfDataManager. By comparing the edit context value with original data we will find the updated values from the EditForm.
If you have defined the Boolean property as Bool, its default value will be false, instead of null. While creating a model for editform, default value (false) will be assigned to it. So we have requested you to define the property as Bool? To overcome the reported issue.
Please get back to us if you have further queries.
Regards,
Vignesh Natarajan
Thanks Vignesh,
One question then.
If that's the cause of the problem, then why don't we have same issue with other non-null column types, like int, decimal, etc, which are NOT configured to be int? or decimal?, are in the same table and are similarly not being edited in the grid, but do not generate this errant behaviour?
Regards,
Phil
Hi Phil,
From your query, we would like to inform you that, we have serialized the data
in below manner;
settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; if (req.Method.Equals(HttpMethod.Patch)) { settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }
string serializedData = options.Data == null ? string.Empty : (options.Data.GetType() == typeof(string) ? (string)options.Data : JsonSerializer.Serialize(options.Data, settings)); |
Suppose if req,Method was Patch means ODataV4 adaptor ignore the default values as well for Int and decimal values.
Before this, we have set the edited data with original data in the compareandremove method as like as follows:
internal static object CompareAndRemove(object data, object original, string key = "") { if (original == null) { return data; } Type myType = data.GetType(); var props = new List<PropertyInfo>(myType.GetProperties()); foreach (PropertyInfo prop in props) { PropertyInfo orgProp = original.GetType().GetProperty(prop.Name); var propertyValue = prop.GetValue(data); if (!(propertyValue == null || propertyValue is string || propertyValue.GetType().GetTypeInfo().IsPrimitive || propertyValue is TimeSpan || propertyValue is decimal || propertyValue is DateTime || propertyValue is IEnumerable || propertyValue is DateTimeOffset || propertyValue is ICollection || propertyValue is Guid || propertyValue.GetType().GetTypeInfo().IsEnum)) { CompareAndRemove(propertyValue, orgProp.GetValue(original)); IList<PropertyInfo> propsOfComplex = new List<PropertyInfo>(myType.GetProperties()); IEnumerable<PropertyInfo> final = propsOfComplex.Where((data) => data.Name != "@odata.etag"); if (!final.Any()) { prop.SetValue(data, null); } } else if (prop.Name != key && prop.Name != "@odata.etag" && propertyValue != null && propertyValue.Equals(orgProp.GetValue(original))) { if (propertyValue is bool && prop.PropertyType == typeof(bool)) { prop.SetValue(data, propertyValue); } else { prop.SetValue(data, null); } } } return data; } |
If request method type doesn't patch means, we just ignore the null values. In
this case, we set int and decimal values as null which values are not edited by
comparing original values.
However, we unable to set null values for Boolean type
column. This is the reason for Boolean values are always send in the OData
request even if we don't edit the Boolean values.
Regards,
Sarvesh
Thanks Sarvesh,
I really don't want to get into critiquing your code, and I have done my absolute best to read your reply to understand if you have actually answered my question. Despite that, and using your own logic:
1) If a data set has the following fields
string Title
bool BoolField
int IntField
and the following record:
{ Title: "This is my title", BoolField: true, IntField: 1 }
is edited with the edit -> Title = "New Title"
The compare and remove SHOULD generate PATCH
a) {Title: "New Title"}
but it is actually generating PATCH:
b) {Title: "New Title", BoolField: true}
As per your response, the CompareAndRemove should be removing both BoolField and IntField in the patch as neither of they have changed and nullability is the same for both and generating a), but b) is instead generated.
If the code is not doing this, then it should be, unless there is a logical reason why not. That's what I asked. Am I missing something here?
Regards,
Phil
Hi Phil,
Sorry for the delay.
Based on your query, we want to explain the process that occurs before sending
a patch request. We have a method called CompareAndRemove that is invoked at
this stage. This method compares the original data with the edited data. If the
values of both properties are the same, we set those values as null. This
applies to all data types such as strings, integers, and so on. However, we do
not assign null values to Boolean properties because Booleans can only have
true or false values and cannot be null.
Instead, we pass the actual value to the Boolean variable. If no value is explicitly assigned to the Boolean variable, it will automatically default to false. This default value can cause improper results when sending the patch request because the absence of a value is interpreted as false.
else if (prop.Name != key && prop.Name != "@odata.etag" && propertyValue != null && propertyValue.Equals(orgProp.GetValue(original))) { if (propertyValue is bool && prop.PropertyType == typeof(bool)) { prop.SetValue(data, propertyValue); } else { prop.SetValue(data, null); } } |
To
overcome this issue, we recommend passing a nullable Boolean. By using a
nullable Boolean, you can explicitly specify whether the value is null, true,
or false, ensuring accurate results when sending the patch request. Kindly
refer the attached the code snippet for your reference.
Regards,
Sarvesh
Hi Sarvesh,
Thanks for your effort but I hope you can see, you are essentially explaining WHY it is wrong, the incorrect result that it generates, and the workaround, rather than just acknowledging its wrong, and undertaking to fix it?
The handling of int and bool and all other non-nullable types should be the same. By your own CompareAndRemove logic:
"This method compares the original data with the edited data. If the values of both properties are the same, we set those values as null. This applies to all data types such as strings, integers, and so on."
You CANNOT be setting (unchanged) result for int to null, by definition, so CompareAndRemove is clearly handling int fields correctly, but not bool. Strings are different, so lets leave that out.
There is no difference in terms of nullability between int and bool, and yet CompareAndRemove handles int correctly & messes up bool exactly for the implementation reason you have described. That's the problem!
I appreciate your effort in responding, but if you're not going to fix it, please just acknowledge CompareAndRemove handles int correctly, but not bool, and even though they are the same in terms of nullability, we are not going to fix it.
Sorry if I'm a bit terse. Its been a while & this is fairly straightforward.
Thanks,
Phil
Hi Phil,
In our HttpHandler, we have implemented a mechanism to handle null values by
ignoring them. For PATCH request, we’ll considered default value also null. This applies to all data types such
as strings, integers, and so on. However, we do not assign null values to
Boolean properties because Booleans can only have true or false values and
cannot be null. Instead, we pass the actual value to the Boolean variable. If
no value is explicitly assigned to the Boolean variable, it will automatically
default to false. This default value can cause improper results when sending
the patch request because the absence of a value is interpreted as false.
settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; if (req.Method.Equals(HttpMethod.Patch)) { settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; } |
Above, we used JSONSerialization settings that ignore the default value while serializing the query for updating. From your query, why do the integer and decimal fields work fine, but not the Boolean field? In the 'CompareandRemove' method, we compare the values and set null for other data types, which prevents issues during serialization. However, for Boolean fields, we are unable to set null values. Even if it is a Boolean data type and we skip setting a value, the default value is automatically set. This is the reason why numeric fields don't have a problem, but Booleans do.
internal static object CompareAndRemove(object data, object original, string key = "") { if (original == null) { return data; }
Type myType = data.GetType(); var props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props) { PropertyInfo orgProp = original.GetType().GetProperty(prop.Name); var propertyValue = prop.GetValue(data); if (!(propertyValue == null || propertyValue is string || propertyValue.GetType().GetTypeInfo().IsPrimitive || propertyValue is TimeSpan || propertyValue is decimal || propertyValue is DateTime || propertyValue is IEnumerable || propertyValue is DateTimeOffset || propertyValue is ICollection || propertyValue is Guid || propertyValue.GetType().GetTypeInfo().IsEnum)) { CompareAndRemove(propertyValue, orgProp.GetValue(original)); IList<PropertyInfo> propsOfComplex = new List<PropertyInfo>(myType.GetProperties()); IEnumerable<PropertyInfo> final = propsOfComplex.Where((data) => data.Name != "@odata.etag"); var settings = new JsonSerializerOptions() { WriteIndented = true }; settings.Converters.Add(new JsonStringEnumConverter()); settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; settings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; string serializedData = JsonSerializer.Serialize(propertyValue, settings); if (!final.Any() || serializedData == "{}") { prop.SetValue(data, null); } } else if (prop.Name != key && prop.Name != "@odata.etag" && propertyValue != null && propertyValue.Equals(orgProp.GetValue(original))) { if (propertyValue is bool && prop.PropertyType == typeof(bool)) { prop.SetValue(data, propertyValue); } else { prop.SetValue(data, null); } } } |
To overcome this issue, we recommend passing a nullable Boolean. By using a
nullable Boolean, you can explicitly specify whether the value is null, true,
or false, ensuring accurate results when sending the patch request. Kindly
refer the attached the code snippet for your reference.
Regards,
Sarvesh