Skip to content

Commit 805aa10

Browse files
committed
Equality comparison of primitive collections was not performed correctly when using MultiMap
1 parent 62f126d commit 805aa10

5 files changed

Lines changed: 65 additions & 10 deletions

File tree

src/Dibix/Mapping/EntityDescriptor.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ internal sealed class EntityDescriptor
88
{
99
private Delegate _postProcessor;
1010

11+
public bool IsPrimitive { get; }
1112
public ICollection<EntityKey> Keys { get; }
1213
public EntityKey Discriminator { get; set; }
1314
public IList<EntityProperty> Properties { get; }
1415

15-
public EntityDescriptor()
16+
public EntityDescriptor(bool isPrimitive)
1617
{
18+
this.IsPrimitive = isPrimitive;
1719
this.Keys = new Collection<EntityKey>();
1820
this.Properties = new Collection<EntityProperty>();
1921
}

src/Dibix/Mapping/EntityDescriptorCache.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ internal static class EntityDescriptorCache
2323

2424
private static EntityDescriptor BuildDescriptor(Type type)
2525
{
26-
EntityDescriptor descriptor = new EntityDescriptor();
27-
if (type.IsPrimitive() || type == typeof(byte[]) || type == typeof(XElement))
26+
bool isPrimitive = type.IsPrimitive();
27+
EntityDescriptor descriptor = new EntityDescriptor(isPrimitive);
28+
if (isPrimitive || type == typeof(byte[]) || type == typeof(XElement))
2829
return descriptor;
2930

3031
IDictionary<PropertyInfo, ICollection<IEntityPropertyFormatter>> formattableProperties = new Dictionary<PropertyInfo, ICollection<IEntityPropertyFormatter>>();

src/Dibix/Mapping/EntityEqualityComparer.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,16 @@ public static bool Equal(object x, object y)
4040
if (x is byte[] binaryX && y is byte[] binaryY)
4141
return Enumerable.SequenceEqual(binaryX, binaryY);
4242

43-
EntityDescriptor entityDescriptor = EntityDescriptorCache.GetDescriptor(x.GetType());
44-
if (!entityDescriptor.Keys.Any())
43+
EntityDescriptor entityDescriptorX = EntityDescriptorCache.GetDescriptor(x.GetType());
44+
EntityDescriptor entityDescriptorY = EntityDescriptorCache.GetDescriptor(y.GetType());
45+
46+
if (entityDescriptorX.IsPrimitive && entityDescriptorY.IsPrimitive)
47+
return Equals(x, y);
48+
49+
if (!entityDescriptorX.Keys.Any())
4550
return false;
4651

47-
return entityDescriptor.Keys.All(a => Equals(a.GetValue(x), a.GetValue(y)));
52+
return entityDescriptorX.Keys.All(a => Equals(a.GetValue(x), a.GetValue(y)));
4853
}
4954

5055
bool IEqualityComparer<object>.Equals(object x, object y) => this.EqualsCore(x, y);

src/Dibix/Mapping/MultiMapper.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@ private object GetCachedEntity(object item)
6767
private static bool ShouldCollectValue(EntityProperty property, object instance, object newValue)
6868
{
6969
object currentValue = property.GetValue(instance);
70-
return !property.IsCollection ? currentValue == null : !Contains(currentValue, newValue);
70+
if (property.IsCollection)
71+
return !Contains(currentValue, newValue);
72+
73+
EntityDescriptor descriptor = EntityDescriptorCache.GetDescriptor(property.EntityType);
74+
if (descriptor.IsPrimitive)
75+
return false; // MultiMap is being used to map a primitive property to a parent entity => Strange
76+
77+
return currentValue == null;
7178
}
7279

7380
private static bool Contains(object collection, object item) => ((IEnumerable)collection).Cast<object>().Contains(item, EntityEqualityComparer.Instance);

tests/Dibix.Dapper.Tests/DapperDatabaseAccessorAutoMultiMapTest.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Collections.ObjectModel;
34
using System.ComponentModel.DataAnnotations;
45
using System.Linq;
@@ -96,7 +97,7 @@ INNER JOIN (VALUES (7, N'black_de', N'black')
9697
});
9798

9899
[TestMethod]
99-
public Task QuerySingle_WithAutoMultiMap_NonEntity_Collection_ReferenceEqualityIsPerformed() => base.ExecuteTest(accessor =>
100+
public Task QuerySingle_WithAutoMultiMap_NonEntityNonPrimitive_Collection_ReferenceEqualityIsPerformed() => base.ExecuteTest(accessor =>
100101
{
101102
const string commandText = @"SELECT [x].[name], [y].[name], [z].[data]
102103
FROM (VALUES (N'feature1')) AS [x]([name])
@@ -115,7 +116,7 @@ INNER JOIN (VALUES (0x1, N'feature1')
115116
});
116117

117118
[TestMethod]
118-
public Task QuerySingle_WithAutoMultiMap_NonEntity_SingleProperty_ReferenceEqualityIsPerformed() => base.ExecuteTest(accessor =>
119+
public Task QuerySingle_WithAutoMultiMap_NonEntityNonPrimitive_SingleProperty_ReferenceEqualityIsPerformed() => base.ExecuteTest(accessor =>
119120
{
120121
const string commandText = @"SELECT [x].[name], [y].[data], [z].[name]
121122
FROM (VALUES (N'product1')) AS [x]([name])
@@ -131,6 +132,42 @@ INNER JOIN (VALUES (N'feature1', N'product1')
131132
Assert.AreEqual("feature2", result.Features[1].Name);
132133
});
133134

135+
[TestMethod]
136+
public Task QuerySingle_WithAutoMultiMap_Primitive_Collection_ReferenceEqualityIsPerformed() => base.ExecuteTest(accessor =>
137+
{
138+
const string commandText = @"SELECT [name] = [x].[feature_name], [name] = [y].[featureitem_name], [z].[featureitem_id]
139+
FROM (VALUES (N'feature1')) AS [x]([feature_name])
140+
INNER JOIN (VALUES (N'black', N'feature1')
141+
, (N'red', N'feature1')) AS [y]([featureitem_name], [feature_name]) ON [x].[feature_name] = [y].[feature_name]
142+
INNER JOIN (VALUES (1, N'feature1')
143+
, (2, N'feature1')) AS [z]([featureitem_id], [feature_name]) ON [x].[feature_name] = [z].[feature_name]";
144+
FeatureEntity result = accessor.QuerySingle<FeatureEntity, FeatureItemEntity, int>(commandText, accessor.Parameters().Build(), "name,featureitem_id");
145+
Assert.AreEqual("feature1", result.Name);
146+
Assert.AreEqual(2, result.Items.Count);
147+
Assert.AreEqual("black", result.Items[0].Name);
148+
Assert.AreEqual("red", result.Items[1].Name);
149+
Assert.AreEqual(2, result.FeatureItemIds.Count);
150+
Assert.AreEqual(1, result.FeatureItemIds[0]);
151+
Assert.AreEqual(2, result.FeatureItemIds[1]);
152+
});
153+
154+
[TestMethod]
155+
public Task QuerySingle_WithAutoMultiMap_Primitive_SingleProperty_NoMappingIsPerformed() => base.ExecuteTest(accessor =>
156+
{
157+
const string commandText = @"SELECT [name] = [x].[product_name], [y].[feature_id], [name] = [z].[feature_name]
158+
FROM (VALUES (N'product1')) AS [x]([product_name])
159+
INNER JOIN (VALUES (1, N'product1')
160+
, (2, N'product1')) AS [y]([feature_id], [product_name]) ON [x].[product_name] = [y].[product_name]
161+
INNER JOIN (VALUES (N'feature1', N'product1')
162+
, (N'feature2', N'product1')) AS [z]([feature_name], [product_name]) ON [x].[product_name] = [z].[product_name]";
163+
ProductEntity result = accessor.QuerySingle<ProductEntity, int, FeatureEntity>(commandText, accessor.Parameters().Build(), "feature_id,name");
164+
Assert.AreEqual("product1", result.Name);
165+
Assert.AreEqual(0, result.FirstFeatureId); // Setting a primitive property using multi map doesn't make sense and is therefore not supported
166+
Assert.AreEqual(2, result.Features.Count);
167+
Assert.AreEqual("feature1", result.Features[0].Name);
168+
Assert.AreEqual("feature2", result.Features[1].Name);
169+
});
170+
134171
private class RecursiveEntity
135172
{
136173
[Key]
@@ -152,13 +189,15 @@ private class FeatureEntity
152189
public IList<FeatureItemEntity> Items { get; }
153190
public IList<DependentFeatureEntity> Dependencies { get; }
154191
public IList<byte[]> Pictures { get; }
192+
public IList<int> FeatureItemIds { get; }
155193

156194
public FeatureEntity()
157195
{
158196
this.Translations = new Collection<TranslationEntity>();
159197
this.Items = new Collection<FeatureItemEntity>();
160198
this.Dependencies = new Collection<DependentFeatureEntity>();
161199
this.Pictures = new Collection<byte[]>();
200+
this.FeatureItemIds = new Collection<int>();
162201
}
163202
}
164203

@@ -199,6 +238,7 @@ private class ProductEntity
199238
public string Name { get; set; }
200239
public byte[] Picture { get; set; }
201240
public IList<FeatureEntity> Features { get; }
241+
public int FirstFeatureId { get; set; }
202242

203243
public ProductEntity()
204244
{

0 commit comments

Comments
 (0)