Skip to content

Commit c35415d

Browse files
committed
Implementation of litedb-org#2722
[Key] / [NotMapped] from DataAnnotations
1 parent 7f3e36f commit c35415d

2 files changed

Lines changed: 125 additions & 7 deletions

File tree

LiteDBX.Tests/Mapper/CustomMapping_Tests.cs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using FluentAssertions;
1+
using System.ComponentModel.DataAnnotations;
2+
using System.ComponentModel.DataAnnotations.Schema;
3+
using FluentAssertions;
24
using Xunit;
35

46
namespace LiteDbX.Tests.Mapper;
@@ -28,14 +30,77 @@ public void Custom_Id_In_Interface()
2830

2931
var obj = new ConcreteClass { CustomId = "myid", Name = "myname" };
3032
var doc = mapper.Serialize(obj) as BsonDocument;
31-
doc["_id"].Should().NotBeNull();
33+
Assert.NotNull(doc);
34+
doc!["_id"].Should().NotBeNull();
3235
doc["_id"].Should().Be("myid");
3336
doc["CustomName"].Should().NotBe(BsonValue.Null);
3437
doc["CustomName"].Should().Be("myname");
3538
doc["Name"].Should().Be(BsonValue.Null);
3639
doc.Keys.ExpectCount(2);
3740
}
3841

42+
[Fact]
43+
public void Key_Attribute_Is_Mapped_As_Id_With_AutoId_Disabled()
44+
{
45+
var mapper = new BsonMapper();
46+
47+
var entityMapper = mapper.GetEntityMapper(typeof(UserWithKeyAttribute));
48+
49+
entityMapper.Id.Should().NotBeNull();
50+
entityMapper.Id.MemberName.Should().Be(nameof(UserWithKeyAttribute.Key));
51+
entityMapper.Id.FieldName.Should().Be("_id");
52+
entityMapper.Id.AutoId.Should().BeFalse();
53+
}
54+
55+
[Fact]
56+
public void Key_Attribute_Serializes_And_Deserializes_As_Id()
57+
{
58+
var mapper = new BsonMapper();
59+
60+
var doc = mapper.Serialize(new UserWithKeyAttribute(42, "John")) as BsonDocument;
61+
62+
Assert.NotNull(doc);
63+
doc!["_id"].Should().Be(42);
64+
doc["Name"].Should().Be("John");
65+
doc.Keys.ExpectCount(2);
66+
67+
var hydrated = mapper.ToObject<UserWithKeyAttribute>(new BsonDocument { ["_id"] = 7, ["Name"] = "Jane" });
68+
69+
hydrated.Key.Should().Be(7);
70+
hydrated.Name.Should().Be("Jane");
71+
}
72+
73+
[Fact]
74+
public void NotMapped_Attribute_Is_Ignored_During_Mapping_And_Serialization()
75+
{
76+
var mapper = new BsonMapper();
77+
78+
var entityMapper = mapper.GetEntityMapper(typeof(ClassWithNotMappedAttribute));
79+
entityMapper.Members.Should().NotContain(x => x.MemberName == nameof(ClassWithNotMappedAttribute.Ignore));
80+
81+
var doc = mapper.Serialize(new ClassWithNotMappedAttribute { Id = 1, Keep = "K", Ignore = "I" }) as BsonDocument;
82+
83+
Assert.NotNull(doc);
84+
doc!["_id"].Should().Be(1);
85+
doc["Keep"].Should().Be("K");
86+
doc.ContainsKey("Ignore").Should().BeFalse();
87+
doc.Keys.ExpectCount(2);
88+
}
89+
90+
[Fact]
91+
public void NotMapped_Attribute_On_Inherited_Property_Is_Ignored()
92+
{
93+
var mapper = new BsonMapper();
94+
95+
var doc = mapper.Serialize(new DerivedClassWithInheritedNotMapped { Id = 9, Keep = "keep", Hidden = "secret" }) as BsonDocument;
96+
97+
Assert.NotNull(doc);
98+
doc!["_id"].Should().Be(9);
99+
doc["Keep"].Should().Be("keep");
100+
doc.ContainsKey("Hidden").Should().BeFalse();
101+
doc.Keys.ExpectCount(2);
102+
}
103+
39104
public class UserWithCustomId
40105
{
41106
public UserWithCustomId(int key, string name)
@@ -58,4 +123,41 @@ public abstract class BaseClass
58123
}
59124

60125
public class ConcreteClass : BaseClass { }
126+
127+
public class UserWithKeyAttribute
128+
{
129+
public UserWithKeyAttribute(int key, string name)
130+
{
131+
Key = key;
132+
Name = name;
133+
}
134+
135+
[Key]
136+
public int Key { get; }
137+
138+
public string Name { get; }
139+
}
140+
141+
public class ClassWithNotMappedAttribute
142+
{
143+
public int Id { get; set; }
144+
145+
public string Keep { get; set; }
146+
147+
[NotMapped]
148+
public string Ignore { get; set; }
149+
}
150+
151+
public abstract class BaseClassWithNotMapped
152+
{
153+
public int Id { get; set; }
154+
155+
[NotMapped]
156+
public string Hidden { get; set; }
157+
}
158+
159+
public class DerivedClassWithInheritedNotMapped : BaseClassWithNotMapped
160+
{
161+
public string Keep { get; set; }
162+
}
61163
}

LiteDBX/Client/Mapper/BsonMapper.GetEntityMapper.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ public partial class BsonMapper
1414
/// </summary>
1515
private readonly ConcurrentDictionary<Type, EntityMapper> _entities = new();
1616

17+
#if NET8_0_OR_GREATER
18+
private static bool HasDataAnnotationsKeyAttribute(MemberInfo memberInfo)
19+
=> CustomAttributeExtensions.IsDefined(memberInfo, typeof(global::System.ComponentModel.DataAnnotations.KeyAttribute), true);
20+
21+
private static bool HasDataAnnotationsNotMappedAttribute(MemberInfo memberInfo)
22+
=> CustomAttributeExtensions.IsDefined(memberInfo, typeof(global::System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute), true);
23+
#else
24+
private static bool HasDataAnnotationsKeyAttribute(MemberInfo _)
25+
=> false;
26+
27+
private static bool HasDataAnnotationsNotMappedAttribute(MemberInfo _)
28+
=> false;
29+
#endif
30+
1731
/// <summary>
1832
/// Get property mapper between typed .NET class and BsonDocument - Cache results
1933
/// </summary>
@@ -66,13 +80,13 @@ protected void BuildEntityMapper(EntityMapper mapper)
6680
var fieldAttr = typeof(BsonFieldAttribute);
6781
var dbrefAttr = typeof(BsonRefAttribute);
6882

69-
var members = GetTypeMembers(mapper.ForType);
83+
var members = GetTypeMembers(mapper.ForType).ToArray();
7084
var id = GetIdMember(members);
7185

7286
foreach (var memberInfo in members)
7387
{
74-
// checks [BsonIgnore]
75-
if (CustomAttributeExtensions.IsDefined(memberInfo, ignoreAttr, true))
88+
// checks [BsonIgnore] / [NotMapped]
89+
if (CustomAttributeExtensions.IsDefined(memberInfo, ignoreAttr, true) || HasDataAnnotationsNotMappedAttribute(memberInfo))
7690
{
7791
continue;
7892
}
@@ -103,6 +117,7 @@ protected void BuildEntityMapper(EntityMapper mapper)
103117
// check if property has [BsonId] to get with was setted AutoId = true
104118
var autoId = (BsonIdAttribute)CustomAttributeExtensions.GetCustomAttributes(memberInfo, idAttr, true)
105119
.FirstOrDefault();
120+
var hasDataAnnotationsKeyAttribute = HasDataAnnotationsKeyAttribute(memberInfo);
106121

107122
// get data type
108123
var dataType = memberInfo is PropertyInfo
@@ -115,7 +130,7 @@ protected void BuildEntityMapper(EntityMapper mapper)
115130
// create a property mapper
116131
var member = new MemberMapper
117132
{
118-
AutoId = autoId == null ? true : autoId.AutoId,
133+
AutoId = autoId?.AutoId ?? !hasDataAnnotationsKeyAttribute,
119134
FieldName = name,
120135
MemberName = memberInfo.Name,
121136
DataType = dataType,
@@ -156,8 +171,9 @@ protected virtual MemberInfo GetIdMember(IEnumerable<MemberInfo> members)
156171
{
157172
return Reflection.SelectMember(members,
158173
x => CustomAttributeExtensions.IsDefined(x, typeof(BsonIdAttribute), true),
174+
x => HasDataAnnotationsKeyAttribute(x),
159175
x => x.Name.Equals("Id", StringComparison.OrdinalIgnoreCase),
160-
x => x.Name.Equals(x.DeclaringType.Name + "Id", StringComparison.OrdinalIgnoreCase));
176+
x => x.DeclaringType != null && x.Name.Equals(x.DeclaringType.Name + "Id", StringComparison.OrdinalIgnoreCase));
161177
}
162178

163179
/// <summary>

0 commit comments

Comments
 (0)