-
Notifications
You must be signed in to change notification settings - Fork 934
Fix deletion of component with null property #1755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This comment has been minimized.
This comment has been minimized.
All commits should be squashed. The last commit may be dropped if preferred: it could be a possible breaking change for external users of the impacted utility methods, if they use it on array of a type implementing |
// - bags delete everything first. | ||
// - indexed collections use their index, which is currently not mappable as a composite index with nullable | ||
// column. All index columns are forced to not-nullable by mapping implementation. When using a formula in | ||
// index, they use the element, but its columns are also forced to not-nullable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This "forced to not-nullable" thing seems to me a bit undue, as this impacts only ddl generation, forbidding to test such cases (unless tweaking the generated schema afterward), but not forbidding users not using schema generation to map such things, and insert null. Then these cases will have the delete issue too, and also an issue on update. But no one seems to ask for nullable support on those cases, so I have not handled them.
return SomeBool == other.SomeBool && string.Equals(SomeString, other.SomeString); | ||
} | ||
|
||
public override bool Equals(object obj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not overriding it causes the set to always delete all its elements, causing the update test to be even more moot. (It is anyway somewhat moot, as with set, updates are handled by delete/insert.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this behaviour needs to be changed (in a separate PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean, if the component reference has not changed, update it instead of deleting/inserting it? But the persistence context loaded state does not keep track of component references and so, does not allow to check for that condition.
Or do you mean we should somewhat "recycle" components to delete by updating them with data of components to add in order to replace as most as possible deletes/inserts by single updates? That would cause actual removals combined with adds to be replaced by updates, which could be quite unexpected for users, and breaking for custom auditing logic (like trigger based auditing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm talking about this behaviour: "Not overriding it causes the set to always delete all its elements" it shouldn't be like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From here:
Note: if you define an
ISet
of composite elements, it is very important to implementEquals()
andGetHashCode()
correctly.
It is in fact an error to map a set of components without a proper equal semantic. The set cannot enforce its semantic in such case: better use a bag instead.
Of course the side effect this error has about flushes, causing the whole set to be purged and recreated, could still be fixed. But that would be fixing a case resulting anyway from a bad mapping/implementation on the user side. Why not if this can be done in a simple way with very few impacts on normal uses, but otherwise, I do not think it should be fixed.
Fixing it will likely require to extract the set content as component values arrays, then searching for each array of values if we can find the same array in the loaded state. This looks to me as a non-negligible overhead, compared to just using the set Contains
method. Moreover it would then ignore equals implementations having another semantic, which will be a possible breaking change.
@@ -468,7 +471,10 @@ public AbstractCollectionPersister(Mapping.Collection collection, ICacheConcurre | |||
?? ExecuteUpdateResultCheckStyle.DetermineDefault(collection.CustomSQLUpdate, updateCallable); | |||
} | |||
|
|||
// 6.0 TODO: call GenerateDeleteRowString(null); instead. | |||
#pragma warning disable 618 | |||
sqlDeleteRowString = GenerateDeleteRowString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not replaced yet for avoiding a possible breaking change for those having overridden it.
Demonstrate nhibernate#1170 - Incorrect query when items removed from a collection of components contain null values
All its current usages are done on type where using the default comparer will not yield different results, excepted in some cases better performances.
7c55f90
to
f9455c0
Compare
Rebased for also updating the documentation. (Fixup commit already squashed.) |
Fix a message spelling
@@ -179,45 +179,12 @@ public static int CountTrue(bool[] array) | |||
|
|||
public static bool ArrayEquals<T>(T[] a, T[] b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out this magic already exists in the .NET Fx:
Array
implements IStructuralEquatable
interface:
var a = new[] {1};
var b = new[] {1};
IStructuralEquatable aa = a;
var @equals = aa.Equals(b, EqualityComparer<int>.Default); //true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that magic works by comparing everything as objects as it uses the non generic IEqualityComparer
interface, causing value types to be boxed.
The ArrayEquals(byte[] a, byte[] b)
overload would be likely less efficient if re-implemented with this.
The way I have re-implemented this (with the last commit) also allows ArrayEquals<T>
to avoid boxing.
I'm using the NH 5.3.5 and I continue dealing with this issue (removing itens from Note that my
|
It is better open a new issue for this, with a more complete example. (With which data does it fails, ...) |
Fixes #1170 - Incorrect query when items removed from a collection
of components contain null values
NH-3634 has added support of
null
valued component properties in select queries comparisons, but they are indeed still not properly supported since this still fails when deleting.