Skip to content

Commit 55255a1

Browse files
sanych-sunOleksandr Poliakov
and
Oleksandr Poliakov
authored
EF-67: KeyNotFoundException does not include key name in SerializationHelper.ReadElementValue (#41)
Co-authored-by: Oleksandr Poliakov <[email protected]>
1 parent 6e088d4 commit 55255a1

File tree

3 files changed

+266
-26
lines changed

3 files changed

+266
-26
lines changed

src/MongoDB.EntityFrameworkCore/Serializers/SerializationHelper.cs

+27-15
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,23 @@ internal static class SerializationHelper
3131
public static T GetPropertyValue<T>(BsonDocument document, IReadOnlyProperty property)
3232
{
3333
var serializationInfo = GetPropertySerializationInfo(property);
34-
return ReadElementValue<T>(document, serializationInfo);
34+
if(TryReadElementValue(document, serializationInfo, out T value) || property.IsNullable)
35+
{
36+
return value;
37+
}
38+
39+
throw new KeyNotFoundException($"Document does not contain value for non-nullable field '{property.Name}'.");
3540
}
3641

3742
public static T GetElementValue<T>(BsonDocument document, string elementName)
3843
{
3944
var serializationInfo = new BsonSerializationInfo(elementName, CreateTypeSerializer(typeof(T)), typeof(T));
40-
return ReadElementValue<T>(document, serializationInfo);
45+
if(TryReadElementValue(document, serializationInfo, out T value) || typeof(T).IsNullableType())
46+
{
47+
return value;
48+
}
49+
50+
throw new KeyNotFoundException($"Document does not contain value for non-nullable field '{elementName}'.");
4151
}
4252

4353
internal static void WriteKeyProperties(IBsonWriter writer, IUpdateEntry entry)
@@ -165,32 +175,34 @@ private static IBsonSerializer CreateArraySerializer(Type elementType)
165175
private static IBsonSerializer CreateListSerializer(Type elementType)
166176
=> (IBsonSerializer)Activator.CreateInstance(typeof(ListSerializer<>).MakeGenericType(elementType));
167177

168-
private static T? ReadElementValue<T>(BsonDocument document, BsonSerializationInfo elementSerializationInfo)
178+
private static bool TryReadElementValue<T>(BsonDocument document, BsonSerializationInfo elementSerializationInfo, out T value)
169179
{
170-
BsonValue rawValue;
180+
BsonValue? rawValue;
171181
if (elementSerializationInfo.ElementPath == null)
172182
{
173-
if (!document.TryGetValue(elementSerializationInfo.ElementName, out rawValue))
174-
{
175-
if (!typeof(T).IsNullableType())
176-
throw new KeyNotFoundException();
177-
178-
return default; // Default missing values if they are nullable
179-
}
183+
document.TryGetValue(elementSerializationInfo.ElementName, out rawValue);
180184
}
181185
else
182186
{
183187
rawValue = document;
184188
foreach (string? node in elementSerializationInfo.ElementPath)
185189
{
186-
rawValue = ((BsonDocument)rawValue)[node];
187-
if (rawValue == null)
190+
var doc = (BsonDocument)rawValue;
191+
if (!doc.TryGetValue(node, out rawValue))
188192
{
189-
return default;
193+
rawValue = null;
194+
break;
190195
}
191196
}
192197
}
193198

194-
return (T)elementSerializationInfo.DeserializeValue(rawValue);
199+
if (rawValue != null)
200+
{
201+
value = (T)elementSerializationInfo.DeserializeValue(rawValue);
202+
return true;
203+
}
204+
205+
value = default;
206+
return false;
195207
}
196208
}

tests/MongoDB.EntityFrameworkCore.FunctionalTests/Serialization/CollectionSerializationTests.cs

+143-11
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public CollectionSerializationTests(TemporaryDatabaseFixture tempDatabase)
2323
}
2424

2525
[Theory]
26-
[InlineData(2, 4, 8, 16, 32, 64)]
2726
[InlineData]
27+
[InlineData(2, 4, 8, 16, 32, 64)]
2828
public void Int_array_round_trips(params int[] expected)
2929
{
3030
var collection = TempDatabase.CreateTemporaryCollection<IntArrayEntity>(nameof(Int_array_round_trips) + expected.Length);
@@ -44,24 +44,61 @@ public void Int_array_round_trips(params int[] expected)
4444
}
4545

4646
[Fact]
47-
public void Missing_int_array_defaults_to_null()
47+
public void Missing_int_array_throws()
4848
{
4949
var collection = SetupIdOnlyCollection<IntArrayEntity>();
5050
using var db = SingleEntityDbContext.Create(collection);
5151

52+
Assert.Throws<KeyNotFoundException>(() => db.Entitites.FirstOrDefault());
53+
}
54+
55+
class IntArrayEntity : BaseIdEntity
56+
{
57+
public int[] anIntArray { get; set; }
58+
}
59+
60+
61+
[Theory]
62+
[InlineData]
63+
[InlineData(2, 4, 8, 16, 32, 64)]
64+
public void Nullable_int_array_round_trips(params int[] expected)
65+
{
66+
var collection = TempDatabase.CreateTemporaryCollection<NullableIntArrayEntity>(nameof(Nullable_int_array_round_trips) + expected.Length);
67+
68+
{
69+
using var db = SingleEntityDbContext.Create(collection);
70+
db.Entitites.Add(new NullableIntArrayEntity {anIntArray = expected});
71+
db.SaveChanges();
72+
}
73+
74+
{
75+
using var db = SingleEntityDbContext.Create(collection);
76+
var result = db.Entitites.FirstOrDefault();
77+
Assert.NotNull(result);
78+
Assert.Equal(expected, result.anIntArray);
79+
}
80+
}
81+
82+
[Fact]
83+
public void Missing_nullable_int_array_defaults_to_null()
84+
{
85+
var collection = SetupIdOnlyCollection<NullableIntArrayEntity>();
86+
using var db = SingleEntityDbContext.Create(collection);
87+
5288
var result = db.Entitites.FirstOrDefault();
5389
Assert.NotNull(result);
5490
Assert.Null(result.anIntArray);
5591
}
5692

57-
class IntArrayEntity : BaseIdEntity
93+
class NullableIntArrayEntity : BaseIdEntity
5894
{
59-
public int[] anIntArray { get; set; }
95+
public int[]? anIntArray { get; set; }
6096
}
6197

98+
6299
[Theory]
63-
[InlineData("abc", "def", "ghi", "and the rest")]
64100
[InlineData]
101+
[InlineData("abc", "def", "ghi", "and the rest")]
65102
public void String_array_round_trips(params string[] expected)
66103
{
67104
var collection =
@@ -82,34 +119,129 @@ public void String_array_round_trips(params string[] expected)
82119
}
83120

84121
[Fact]
85-
public void Missing_string_array_defaults_to_null()
122+
public void Missing_string_array_throws()
86123
{
87124
var collection = SetupIdOnlyCollection<StringArrayEntity>();
88125
using var db = SingleEntityDbContext.Create(collection);
89126

127+
Assert.Throws<KeyNotFoundException>(() => db.Entitites.FirstOrDefault());
128+
}
129+
130+
class StringArrayEntity : BaseIdEntity
131+
{
132+
public string[] aStringArray { get; set; }
133+
}
134+
135+
136+
[Theory]
137+
[InlineData]
138+
[InlineData("abc", "def", "ghi", "and the rest")]
139+
public void Nullable_string_array_round_trips(params string[] expected)
140+
{
141+
var collection =
142+
TempDatabase.CreateTemporaryCollection<NullableStringArrayEntity>(nameof(Nullable_string_array_round_trips) + expected.Length);
143+
144+
{
145+
using var db = SingleEntityDbContext.Create(collection);
146+
db.Entitites.Add(new NullableStringArrayEntity {aStringArray = expected});
147+
db.SaveChanges();
148+
}
149+
150+
{
151+
using var db = SingleEntityDbContext.Create(collection);
152+
var result = db.Entitites.FirstOrDefault();
153+
Assert.NotNull(result);
154+
Assert.Equal(expected, result.aStringArray);
155+
}
156+
}
157+
158+
[Fact]
159+
public void Missing_nullable_string_array_defaults_to_null()
160+
{
161+
var collection = SetupIdOnlyCollection<NullableStringArrayEntity>();
162+
using var db = SingleEntityDbContext.Create(collection);
163+
90164
var result = db.Entitites.FirstOrDefault();
91165
Assert.NotNull(result);
92166
Assert.Null(result.aStringArray);
93167
}
94168

95-
class StringArrayEntity : BaseIdEntity
169+
class NullableStringArrayEntity : BaseIdEntity
96170
{
97-
public string[] aStringArray { get; set; }
171+
public string[]? aStringArray { get; set; }
172+
}
173+
174+
[Theory]
175+
[InlineData]
176+
[InlineData(1, 2, 3, 4)]
177+
public void List_round_trips(params int[] expected)
178+
{
179+
var collection =
180+
TempDatabase.CreateTemporaryCollection<ListEntity>(nameof(List_round_trips) + expected.Length);
181+
182+
{
183+
using var db = SingleEntityDbContext.Create(collection);
184+
db.Entitites.Add(new ListEntity {aList = new List<int>(expected)});
185+
db.SaveChanges();
186+
}
187+
188+
{
189+
using var db = SingleEntityDbContext.Create(collection);
190+
var result = db.Entitites.FirstOrDefault();
191+
Assert.NotNull(result);
192+
Assert.Equivalent(expected, result.aList);
193+
}
98194
}
99195

100196
[Fact]
101-
public void Missing_list_defaults_to_null()
197+
public void Missing_list_throws()
102198
{
103199
var collection = SetupIdOnlyCollection<ListEntity>();
104200
using var db = SingleEntityDbContext.Create(collection);
105201

202+
Assert.Throws<KeyNotFoundException>(() => db.Entitites.FirstOrDefault());
203+
}
204+
205+
class ListEntity : BaseIdEntity
206+
{
207+
public List<int> aList { get; set; }
208+
}
209+
210+
[Theory]
211+
[InlineData]
212+
[InlineData(1, 2, 3, 4)]
213+
public void Nullable_list_round_trips(params int[] expected)
214+
{
215+
var collection =
216+
TempDatabase.CreateTemporaryCollection<NullableListEntity>(nameof(Nullable_list_round_trips) + expected.Length);
217+
218+
{
219+
using var db = SingleEntityDbContext.Create(collection);
220+
db.Entitites.Add(new NullableListEntity {aList = new List<int>(expected)});
221+
db.SaveChanges();
222+
}
223+
224+
{
225+
using var db = SingleEntityDbContext.Create(collection);
226+
var result = db.Entitites.FirstOrDefault();
227+
Assert.NotNull(result);
228+
Assert.Equivalent(expected, result.aList);
229+
}
230+
}
231+
232+
[Fact]
233+
public void Missing_nullable_list_default_to_null()
234+
{
235+
var collection = SetupIdOnlyCollection<NullableListEntity>();
236+
using var db = SingleEntityDbContext.Create(collection);
237+
106238
var result = db.Entitites.FirstOrDefault();
107239
Assert.NotNull(result);
108240
Assert.Null(result.aList);
109241
}
110242

111-
class ListEntity : BaseIdEntity
243+
class NullableListEntity : BaseIdEntity
112244
{
113-
public List<decimal> aList { get; set; }
245+
public List<int>? aList { get; set; }
114246
}
115247
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using MongoDB.Bson;
3+
using MongoDB.EntityFrameworkCore.Metadata.Conventions;
4+
using MongoDB.EntityFrameworkCore.Serializers;
5+
6+
namespace MongoDB.EntityFrameworkCore.UnitTests.Serializers;
7+
8+
public class SerializationHelperTests
9+
{
10+
[Fact]
11+
public void Read_element_returns_value()
12+
{
13+
var document = BsonDocument.Parse("{ property: 12 }");
14+
15+
var value = SerializationHelper.GetElementValue<int>(document, "property");
16+
17+
Assert.Equal(12, value);
18+
}
19+
20+
[Fact]
21+
public void Read_missing_element_throws()
22+
{
23+
var document = BsonDocument.Parse("{ property: 12 }");
24+
25+
var exception = Record.Exception(() => SerializationHelper.GetElementValue<int>(document, "missedElementName"));
26+
27+
Assert.IsType<KeyNotFoundException>(exception);
28+
Assert.Contains("missedElementName", exception.Message);
29+
}
30+
31+
[Fact]
32+
public void Read_missing_nullable_element_returns_default()
33+
{
34+
var document = BsonDocument.Parse("{ property: 12 }");
35+
36+
var value = SerializationHelper.GetElementValue<int?>(document, "missedElementName");
37+
38+
Assert.Null(value);
39+
}
40+
41+
[Fact]
42+
public void Read_property_value()
43+
{
44+
var conventions = MongoConventionSetBuilder.Build();
45+
var modelBuilder = new ModelBuilder(conventions);
46+
modelBuilder.Entity<TestEntity>();
47+
48+
var entity = modelBuilder.Model.FindEntityType(typeof(TestEntity));
49+
var property = entity.GetProperty(nameof(TestEntity.IntProperty));
50+
var document = BsonDocument.Parse("{ IntProperty: 12 }");
51+
52+
var value = SerializationHelper.GetPropertyValue<int>(document, property);
53+
54+
Assert.Equal(12, value);
55+
}
56+
57+
[Fact]
58+
public void Read_missing_property_throws()
59+
{
60+
var conventions = MongoConventionSetBuilder.Build();
61+
var modelBuilder = new ModelBuilder(conventions);
62+
modelBuilder.Entity<TestEntity>();
63+
64+
var entity = modelBuilder.Model.FindEntityType(typeof(TestEntity));
65+
var property = entity.GetProperty(nameof(TestEntity.IntProperty));
66+
var document = BsonDocument.Parse("{ property: 12 }");
67+
68+
var exception = Record.Exception(() => SerializationHelper.GetPropertyValue<int>(document, property));
69+
70+
Assert.IsType<KeyNotFoundException>(exception);
71+
Assert.Contains("IntProperty", exception.Message);
72+
}
73+
74+
[Fact]
75+
public void Read_missing_nullable_property_returns_default()
76+
{
77+
var conventions = MongoConventionSetBuilder.Build();
78+
var modelBuilder = new ModelBuilder(conventions);
79+
modelBuilder.Entity<TestEntity>();
80+
81+
var entity = modelBuilder.Model.FindEntityType(typeof(TestEntity));
82+
var property = entity.GetProperty(nameof(TestEntity.NullableProperty));
83+
var document = BsonDocument.Parse("{ somevalue: 12 }");
84+
85+
var value = SerializationHelper.GetPropertyValue<int?>(document, property);
86+
87+
Assert.Null(value);
88+
}
89+
90+
public class TestEntity
91+
{
92+
public int IntProperty { get; set; }
93+
94+
public int? NullableProperty { get; set; }
95+
}
96+
}

0 commit comments

Comments
 (0)