/
IdPropertyFinder.cs
166 lines (150 loc) · 5.82 KB
/
IdPropertyFinder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Norm.Configuration;
namespace Norm.BSON
{
///<summary>
/// Determines the best property to be used as the identifier property.
///</summary>
public class IdPropertyFinder
{
private readonly Dictionary<IdType, PropertyInfo> _idDictionary;
private readonly Type _type;
private PropertyInfo[] _properties;
private PropertyInfo[] _interfaceProperties;
///<summary>
/// Initializes new IdPropertyFinder.
///</summary>
///<param name="type">The type for which an id property needs to be identified.</param>
public IdPropertyFinder(Type type)
{
_type = type;
_idDictionary = new Dictionary<IdType, PropertyInfo>(4)
{
{ IdType.MongoDefault, null },
{ IdType.MapDefined, null },
{ IdType.AttributeDefined, null },
{ IdType.Conventional, null }
};
}
///<summary>
/// Initializes new IdPropertyFinder.
/// Use this constructor to limit the properties you want to test.
///</summary>
///<param name="type">The type for which an id property needs to be identified.</param>
///<param name="properties">The candidate properties fo the type.</param>
public IdPropertyFinder(Type type, PropertyInfo[] properties)
: this(type)
{
_properties = properties;
}
///<summary>
/// Returns the property determined to be the Id with the following priority.
/// Property named _id
/// Explicitly mapped Id property
/// Attribute defined Id property
/// Property named Id
/// Conflicts result in MongoConfigurationMapException.
///</summary>
public PropertyInfo IdProperty
{
get
{
AddCandidates();
CheckForConflictingCandidates();
return _idDictionary.Values.FirstOrDefault(value => value != null);
}
}
/// <summary>
/// Determines if the Id has been explicitly defined in a MongoConfigurationMap <see cref="MongoConfigurationMap"/>.
/// </summary>
/// <param name="idPropertyCandidate">The property name.</param>
private bool PropertyIsExplicitlyMappedToId(string idPropertyCandidate)
{
var map = MongoTypeConfiguration.PropertyMaps;
if (map.ContainsKey(_type))
{
if (map[_type].ContainsKey(idPropertyCandidate))
{
return map[_type][idPropertyCandidate].IsId;
}
}
return false;
}
private void CheckForConflictingCandidates()
{
//This could be written as one line (MongoDefault && (MapDefined || AttributeDefined)) but two lines is more clearer.
//This has the potential to become cumbersome should we discover more conflicts.
if (_idDictionary[IdType.MongoDefault] != null)
{
if (_idDictionary[IdType.MapDefined] != null || _idDictionary[IdType.AttributeDefined] != null)
{
throw new MongoConfigurationMapException(_type.Name + " exposes a property called _id and defines a an Id using MongoIndentifier or by explicit mapping.");
}
}
}
private static bool HasMongoIdentifierAttribute(ICustomAttributeProvider idPropertyCandidate)
{
return idPropertyCandidate.GetCustomAttributes(BsonHelper.MongoIdentifierAttribute, true).Length > 0;
}
private bool PropertyIsAttributeDefinedId(MemberInfo idPropertyCandidate)
{
if (HasMongoIdentifierAttribute(idPropertyCandidate))
{
return true;
}
if (_interfaceProperties != null)
{
var interfacePropertiesWithSameNameAsCandidate = _interfaceProperties.Where(propertyInfo => propertyInfo.Name == idPropertyCandidate.Name);
foreach (PropertyInfo nextProperty in interfacePropertiesWithSameNameAsCandidate)
{
if (HasMongoIdentifierAttribute(nextProperty))
{
return true;
}
}
}
return false;
}
private void AddCandidate(PropertyInfo property)
{
if (PropertyIsExplicitlyMappedToId(property.Name))
{
_idDictionary[IdType.MapDefined] = property;
}
else if (PropertyIsAttributeDefinedId(property))
{
_idDictionary[IdType.AttributeDefined] = property;
}
else if (property.Name.Equals("_id", StringComparison.InvariantCultureIgnoreCase))
{
_idDictionary[IdType.MongoDefault] = property;
}
else if (property.Name.Equals("Id", StringComparison.InvariantCultureIgnoreCase))
{
_idDictionary[IdType.Conventional] = property;
}
}
private void AddCandidates()
{
if(_properties == null)
{
_properties = TypeHelper.GetProperties(_type);
}
_interfaceProperties = TypeHelper.GetInterfaceProperties(_type);
foreach (var property in _properties)
{
AddCandidate(property);
}
}
private enum IdType
{
MapDefined,
AttributeDefined,
MongoDefault,
Conventional,
}
}
}