Skip to content

Commit 24813c2

Browse files
committed
added default constructor to BsonElementAttribute. removed constraints related to mapping readonly fields and properties. added support for persisting readonly fields and properties and not deserializing them.
1 parent 8bf97a8 commit 24813c2

File tree

8 files changed

+215
-16
lines changed

8 files changed

+215
-16
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ Help
2727

2828
#Nupkg artifacts
2929
*.nupkg
30+
31+
# NCrunch artifacts
32+
*.ncrunch*

Bson/Serialization/Attributes/BsonElementAttribute.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public class BsonElementAttribute : BsonSerializationOptionsAttribute
3131
private int _order = int.MaxValue;
3232

3333
// constructors
34+
/// <summary>
35+
/// Initializes a new instance of the BsonElementAttribute class.
36+
/// </summary>
37+
public BsonElementAttribute()
38+
{ }
39+
3440
/// <summary>
3541
/// Initializes a new instance of the BsonElementAttribute class.
3642
/// </summary>

Bson/Serialization/BsonClassMap.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,10 @@ private BsonMemberMap AutoMapMember(MemberInfo memberInfo)
10851085
var elementAttribute = attribute as BsonElementAttribute;
10861086
if (elementAttribute != null)
10871087
{
1088-
memberMap.SetElementName(elementAttribute.ElementName);
1088+
if (!string.IsNullOrEmpty(elementAttribute.ElementName))
1089+
{
1090+
memberMap.SetElementName(elementAttribute.ElementName);
1091+
}
10891092
memberMap.SetOrder(elementAttribute.Order);
10901093
continue;
10911094
}
@@ -1191,7 +1194,7 @@ private IEnumerable<MemberInfo> FindMembers()
11911194
foreach (var fieldInfo in _classType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
11921195
{
11931196
var elementAttribute = (BsonElementAttribute)fieldInfo.GetCustomAttributes(typeof(BsonElementAttribute), false).FirstOrDefault();
1194-
if (elementAttribute == null || fieldInfo.IsInitOnly || fieldInfo.IsLiteral)
1197+
if (elementAttribute == null)
11951198
{
11961199
continue;
11971200
}
@@ -1206,7 +1209,7 @@ private IEnumerable<MemberInfo> FindMembers()
12061209
foreach (var propertyInfo in _classType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
12071210
{
12081211
var elementAttribute = (BsonElementAttribute)propertyInfo.GetCustomAttributes(typeof(BsonElementAttribute), false).FirstOrDefault();
1209-
if (elementAttribute == null || !propertyInfo.CanRead || (!propertyInfo.CanWrite && !_isAnonymous))
1212+
if (elementAttribute == null)
12101213
{
12111214
continue;
12121215
}

Bson/Serialization/BsonClassMapSerializer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,15 @@ public object Deserialize(
142142
}
143143

144144
var memberMap = classMap.GetMemberMapForElement(elementName);
145-
if (memberMap != null && memberMap != classMap.ExtraElementsMemberMap)
145+
if (memberMap != null && memberMap != classMap.ExtraElementsMemberMap && !memberMap.IsReadOnly)
146146
{
147147
DeserializeMember(bsonReader, obj, memberMap);
148148
missingElementMemberMaps.Remove(memberMap);
149149
}
150+
else if (memberMap != null && memberMap.IsReadOnly)
151+
{
152+
bsonReader.SkipValue();
153+
}
150154
else
151155
{
152156
if (classMap.ExtraElementsMemberMap != null)
@@ -171,6 +175,11 @@ public object Deserialize(
171175

172176
foreach (var memberMap in missingElementMemberMaps)
173177
{
178+
if (memberMap.IsReadOnly)
179+
{
180+
continue;
181+
}
182+
174183
if (memberMap.IsRequired)
175184
{
176185
var fieldOrProperty = (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";

Bson/Serialization/BsonMemberMap.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,34 @@ public object DefaultValue
240240
get { return _defaultValue; }
241241
}
242242

243+
/// <summary>
244+
/// Gets whether the member is readonly.
245+
/// </summary>
246+
/// <remarks>
247+
/// Readonly indicates that the member is written to the database, but not read from the database.
248+
/// </remarks>
249+
public bool IsReadOnly
250+
{
251+
get
252+
{
253+
switch(_memberInfo.MemberType)
254+
{
255+
case MemberTypes.Field:
256+
var field = (FieldInfo)_memberInfo;
257+
return field.IsInitOnly || field.IsLiteral;
258+
case MemberTypes.Property:
259+
var property = (PropertyInfo)_memberInfo;
260+
return !property.CanWrite;
261+
default:
262+
throw new NotSupportedException(
263+
string.Format("Only fields and properties are supported by BsonMemberMap. The member {0} of class {1} is a {2}.",
264+
_memberInfo.Name,
265+
_memberInfo.DeclaringType.Name,
266+
_memberInfo.MemberType));
267+
}
268+
}
269+
}
270+
243271
// public methods
244272
/// <summary>
245273
/// Applies the default value to the member of an object.
@@ -497,10 +525,10 @@ private Action<object, object> GetFieldSetter()
497525
{
498526
var fieldInfo = (FieldInfo)_memberInfo;
499527

500-
if (fieldInfo.IsInitOnly || fieldInfo.IsLiteral)
528+
if (IsReadOnly)
501529
{
502530
var message = string.Format(
503-
"The field '{0} {1}' of class '{2}' is readonly.",
531+
"The field '{0} {1}' of class '{2}' is readonly. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.",
504532
fieldInfo.FieldType.FullName, fieldInfo.Name, fieldInfo.DeclaringType.FullName);
505533
throw new BsonSerializationException(message);
506534
}
@@ -554,10 +582,10 @@ private Action<object, object> GetPropertySetter()
554582
{
555583
var propertyInfo = (PropertyInfo)_memberInfo;
556584
var setMethodInfo = propertyInfo.GetSetMethod(true);
557-
if (setMethodInfo == null)
585+
if (IsReadOnly)
558586
{
559587
var message = string.Format(
560-
"The property '{0} {1}' of class '{2}' has no 'set' accessor.",
588+
"The property '{0} {1}' of class '{2}' has no 'set' accessor. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.",
561589
propertyInfo.PropertyType.FullName, propertyInfo.Name, propertyInfo.DeclaringType.FullName);
562590
throw new BsonSerializationException(message);
563591
}

BsonUnitTests/DefaultSerializer/BsonClassMapTests.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ private class A
4040
public int FieldMapped;
4141
[BsonElement("FieldMappedByAttribute")]
4242
private int fieldMappedByAttribute;
43+
[BsonElement]
44+
private readonly int fieldMappedByAttribute2;
4345

4446
public int PropertyMapped { get; set; }
4547
public int PropertyMapped2 { get; private set; }
@@ -49,14 +51,25 @@ private class A
4951

5052
[BsonElement("PropertyMappedByAttribute")]
5153
private int PropertyMappedByAttribute { get; set; }
54+
55+
[BsonElement]
56+
public int PropertyMappedByAttribute2
57+
{
58+
get { return PropertyMapped + 1; }
59+
}
60+
61+
public A()
62+
{
63+
fieldMappedByAttribute2 = 10;
64+
}
5265
}
5366
#pragma warning restore
5467

5568
[Test]
5669
public void TestMappingPicksUpAllMembersWithAttributes()
5770
{
5871
var classMap = BsonClassMap.LookupClassMap(typeof(A));
59-
Assert.AreEqual(6, classMap.AllMemberMaps.Count());
72+
Assert.AreEqual(8, classMap.AllMemberMaps.Count());
6073
}
6174
}
6275

BsonUnitTests/DefaultSerializer/BsonDefaultSerializerTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,26 @@ static Employee()
7575
cm.MapProperty(e => e.FirstName).SetElementName("fn");
7676
cm.MapProperty(e => e.LastName).SetElementName("ln");
7777
cm.MapProperty(e => e.DateOfBirth).SetElementName("dob").SetSerializer(new DateOfBirthSerializer());
78+
cm.MapProperty(e => e.Age).SetElementName("age");
7879
});
7980
}
8081

8182
public ObjectId EmployeeId { get; set; }
8283
public string FirstName { get; set; }
8384
public string LastName { get; set; }
8485
public DateTime DateOfBirth { get; set; }
86+
public int Age
87+
{
88+
get
89+
{
90+
DateTime now = DateTime.Today;
91+
int age = now.Year - DateOfBirth.Year;
92+
if (DateOfBirth > now.AddYears(-age))
93+
age--;
94+
95+
return age;
96+
}
97+
}
8598
}
8699

87100
[Test]

BsonUnitTests/DefaultSerializer/BsonMemberMapTests.cs

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,23 @@ public class BsonMemberMapTests
3131
{
3232
private class TestClass
3333
{
34+
public readonly int ReadOnlyField;
35+
3436
public int Field;
3537

3638
public int Property { get; set; }
3739

38-
public int ReadOnlyProperty { get; private set; }
40+
public int PrivateSettableProperty { get; private set; }
41+
42+
public int ReadOnlyProperty
43+
{
44+
get { return Property + 1; }
45+
}
3946

4047
public TestClass()
4148
{
42-
ReadOnlyProperty = 10;
49+
ReadOnlyField = 13;
50+
PrivateSettableProperty = 10;
4351
}
4452
}
4553

@@ -55,6 +63,15 @@ public void TestGettingAField()
5563
Assert.AreEqual(42, value);
5664
}
5765

66+
[Test]
67+
public void TestIsReadOnlyPropertyOfAField()
68+
{
69+
var classMap = new BsonClassMap<TestClass>(cm => cm.AutoMap());
70+
var memberMap = classMap.GetMemberMap("Field");
71+
72+
Assert.IsFalse(memberMap.IsReadOnly);
73+
}
74+
5875
[Test]
5976
public void TestSettingAField()
6077
{
@@ -67,6 +84,50 @@ public void TestSettingAField()
6784
Assert.AreEqual(42, instance.Field);
6885
}
6986

87+
[Test]
88+
public void TestGettingAReadOnlyField()
89+
{
90+
var instance = new TestClass();
91+
var classMap = new BsonClassMap<TestClass>(cm =>
92+
{
93+
cm.AutoMap();
94+
cm.MapMember(c => c.ReadOnlyField);
95+
});
96+
var memberMap = classMap.GetMemberMap("ReadOnlyField");
97+
98+
int value = (int)memberMap.Getter(instance);
99+
100+
Assert.AreEqual(13, value);
101+
}
102+
103+
[Test]
104+
public void TestIsReadOnlyPropertyOfAReadOnlyField()
105+
{
106+
var classMap = new BsonClassMap<TestClass>(cm =>
107+
{
108+
cm.AutoMap();
109+
cm.MapMember(c => c.ReadOnlyField);
110+
});
111+
var memberMap = classMap.GetMemberMap("ReadOnlyField");
112+
113+
Assert.IsTrue(memberMap.IsReadOnly);
114+
}
115+
116+
[Test]
117+
[ExpectedException(typeof(BsonSerializationException), ExpectedMessage = "The field 'System.Int32 ReadOnlyField' of class 'MongoDB.BsonUnitTests.Serialization.BsonMemberMapTests+TestClass' is readonly. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.")]
118+
public void TestSettingAReadOnlyField()
119+
{
120+
var instance = new TestClass();
121+
var classMap = new BsonClassMap<TestClass>(cm =>
122+
{
123+
cm.AutoMap();
124+
cm.MapMember(c => c.ReadOnlyField);
125+
});
126+
var memberMap = classMap.GetMemberMap("ReadOnlyField");
127+
128+
memberMap.Setter(instance, 12);
129+
}
130+
70131
[Test]
71132
public void TestGettingAProperty()
72133
{
@@ -79,6 +140,15 @@ public void TestGettingAProperty()
79140
Assert.AreEqual(42, value);
80141
}
81142

143+
[Test]
144+
public void TestIsReadOnlyPropertyOfAProperty()
145+
{
146+
var classMap = new BsonClassMap<TestClass>(cm => cm.AutoMap());
147+
var memberMap = classMap.GetMemberMap("Property");
148+
149+
Assert.IsFalse(memberMap.IsReadOnly);
150+
}
151+
82152
[Test]
83153
public void TestSettingAProperty()
84154
{
@@ -92,27 +162,81 @@ public void TestSettingAProperty()
92162
}
93163

94164
[Test]
95-
public void TestGettingAReadOnlyProperty()
165+
public void TestGettingAPrivateSettableProperty()
96166
{
97167
var instance = new TestClass();
98168
var classMap = new BsonClassMap<TestClass>(cm => cm.AutoMap());
99-
var memberMap = classMap.GetMemberMap("ReadOnlyProperty");
169+
var memberMap = classMap.GetMemberMap("PrivateSettableProperty");
100170

101171
int value = (int)memberMap.Getter(instance);
102172

103173
Assert.AreEqual(10, value);
104174
}
105175

106176
[Test]
107-
public void TestSettingAReadOnlyProperty()
177+
public void TestIsReadOnlyPropertyOfAPrivateSettableProperty()
178+
{
179+
var classMap = new BsonClassMap<TestClass>(cm => cm.AutoMap());
180+
var memberMap = classMap.GetMemberMap("PrivateSettableProperty");
181+
182+
Assert.IsFalse(memberMap.IsReadOnly);
183+
}
184+
185+
[Test]
186+
public void TestSettingAPrivateSettableProperty()
108187
{
109188
var instance = new TestClass();
110189
var classMap = new BsonClassMap<TestClass>(cm => cm.AutoMap());
111-
var memberMap = classMap.GetMemberMap("ReadOnlyProperty");
190+
var memberMap = classMap.GetMemberMap("PrivateSettableProperty");
112191

113192
memberMap.Setter(instance, 42);
114193

115-
Assert.AreEqual(42, instance.ReadOnlyProperty);
194+
Assert.AreEqual(42, instance.PrivateSettableProperty);
195+
}
196+
197+
[Test]
198+
public void TestGettingAReadOnlyProperty()
199+
{
200+
var instance = new TestClass { Property = 10 };
201+
var classMap = new BsonClassMap<TestClass>(cm =>
202+
{
203+
cm.AutoMap();
204+
cm.MapMember(c => c.ReadOnlyProperty);
205+
});
206+
207+
var memberMap = classMap.GetMemberMap("ReadOnlyProperty");
208+
209+
int value = (int)memberMap.Getter(instance);
210+
211+
Assert.AreEqual(11, value);
212+
}
213+
214+
[Test]
215+
public void TestIsReadOnlyPropertyOfAReadOnlyProperty()
216+
{
217+
var classMap = new BsonClassMap<TestClass>(cm =>
218+
{
219+
cm.AutoMap();
220+
cm.MapMember(c => c.ReadOnlyProperty);
221+
});
222+
var memberMap = classMap.GetMemberMap("ReadOnlyProperty");
223+
224+
Assert.IsTrue(memberMap.IsReadOnly);
225+
}
226+
227+
[Test]
228+
[ExpectedException(typeof(BsonSerializationException), ExpectedMessage = "The property 'System.Int32 ReadOnlyProperty' of class 'MongoDB.BsonUnitTests.Serialization.BsonMemberMapTests+TestClass' has no 'set' accessor. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.")]
229+
public void TestSettingAReadOnlyProperty()
230+
{
231+
var instance = new TestClass { Property = 10 };
232+
var classMap = new BsonClassMap<TestClass>(cm =>
233+
{
234+
cm.AutoMap();
235+
cm.MapMember(c => c.ReadOnlyProperty);
236+
});
237+
var memberMap = classMap.GetMemberMap("ReadOnlyProperty");
238+
239+
memberMap.Setter(instance, 12);
116240
}
117241
}
118242
}

0 commit comments

Comments
 (0)