Skip to content

Commit 15fee30

Browse files
committed
Merge pull request #1145 from elasticsearch/feature/idproperty-in-code
Add ability to configure id properties in code
2 parents 532fd68 + eb0e48e commit 15fee30

File tree

6 files changed

+172
-0
lines changed

6 files changed

+172
-0
lines changed

src/Nest/Domain/Connection/ConnectionSettings.cs

+24
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ string IConnectionSettingsValues.DefaultIndex
9090
private ReadOnlyCollection<Func<Type, JsonConverter>> _contractConverters;
9191
ReadOnlyCollection<Func<Type, JsonConverter>> IConnectionSettingsValues.ContractConverters { get { return _contractConverters; } }
9292

93+
private FluentDictionary<Type, string> _idProperties = new FluentDictionary<Type, string>();
94+
FluentDictionary<Type, string> IConnectionSettingsValues.IdProperties { get { return _idProperties; } }
95+
9396
private FluentDictionary<MemberInfo, PropertyMapping> _propertyMappings = new FluentDictionary<MemberInfo, PropertyMapping>();
9497
FluentDictionary<MemberInfo, PropertyMapping> IConnectionSettingsValues.PropertyMappings { get { return _propertyMappings; } }
9598

@@ -205,6 +208,27 @@ public T MapDefaultTypeNames(Action<FluentDictionary<Type, string>> mappingSelec
205208
return (T)this;
206209
}
207210

211+
public T MapIdPropertyFor<TDocument>(Expression<Func<TDocument, object>> objectPath)
212+
{
213+
objectPath.ThrowIfNull("objectPath");
214+
215+
var memberInfo = new MemberInfoResolver(this, objectPath);
216+
var propertyName = memberInfo.Members.Single().Name;
217+
218+
if (this._idProperties.ContainsKey(typeof(TDocument)))
219+
{
220+
if (this._idProperties[typeof(TDocument)].Equals(propertyName))
221+
return (T)this;
222+
223+
throw new ArgumentException("Cannot map '{0}' as the id property for type '{1}': it already has '{2}' mapped."
224+
.F(propertyName, typeof(TDocument).Name, this._idProperties[typeof(TDocument)]));
225+
}
226+
227+
this._idProperties.Add(typeof(TDocument), propertyName);
228+
229+
return (T)this;
230+
}
231+
208232
public T MapPropertiesFor<TDocument>(Action<PropertyMappingDescriptor<TDocument>> propertiesSelector)
209233
{
210234
propertiesSelector.ThrowIfNull("propertiesSelector");

src/Nest/Domain/Connection/IConnectionSettingsValues.cs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public interface IConnectionSettingsValues : IConnectionConfigurationValues
1111
ElasticInferrer Inferrer { get; }
1212
FluentDictionary<Type, string> DefaultIndices { get; }
1313
FluentDictionary<Type, string> DefaultTypeNames { get; }
14+
FluentDictionary<Type, string> IdProperties { get; }
1415
FluentDictionary<MemberInfo, PropertyMapping> PropertyMappings { get; }
1516
string DefaultIndex { get; }
1617
Func<string, string> DefaultPropertyNameInferrer { get; }

src/Nest/ExposedInternals/ElasticInferrer.cs

+6
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ public string IndexNames(IEnumerable<IndexNameMarker> indices)
127127
public string Id<T>(T obj) where T : class
128128
{
129129
if (obj == null) return null;
130+
131+
string idProperty;
132+
this._connectionSettings.IdProperties.TryGetValue(typeof(T), out idProperty);
133+
if (!idProperty.IsNullOrEmpty())
134+
return this.IdResolver.GetIdFor(obj, idProperty);
135+
130136
return this.IdResolver.GetIdFor(obj);
131137
}
132138

src/Nest/Resolvers/IdResolver.cs

+17
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ internal static Func<T, object> MakeDelegate<T, U>(MethodInfo @get)
2424
return t => f(t);
2525
}
2626

27+
public string GetIdFor<T>(T @object, string idProperty)
28+
{
29+
try
30+
{
31+
var value = @object
32+
.GetType()
33+
.GetProperty(idProperty)
34+
.GetValue(@object, null);
35+
36+
return value.ToString();
37+
}
38+
catch
39+
{
40+
return null;
41+
}
42+
}
43+
2744
public string GetIdFor<T>(T @object)
2845
{
2946
if (@object == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using FluentAssertions;
2+
using Elasticsearch.Net.Connection;
3+
using Nest.Tests.MockData.Domain;
4+
using NUnit.Framework;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Nest.Tests.Unit.Internals.Inferno
12+
{
13+
[TestFixture]
14+
public class MapIdPropertyForTests : BaseJsonTests
15+
{
16+
[Test]
17+
public void MapNumericIdProperty()
18+
{
19+
var settings = new ConnectionSettings()
20+
.MapIdPropertyFor<ElasticsearchProject>(p => p.LongValue);
21+
22+
var client = new ElasticClient(settings, connection: new InMemoryConnection());
23+
24+
var project = new ElasticsearchProject { LongValue = 123 };
25+
26+
Assert.AreEqual(project.LongValue.ToString(), client.Infer.Id<ElasticsearchProject>(project));
27+
}
28+
29+
[Test]
30+
public void MapStringIdProperty()
31+
{
32+
var settings = new ConnectionSettings()
33+
.MapIdPropertyFor<ElasticsearchProject>(p => p.Name);
34+
35+
var client = new ElasticClient(settings, connection: new InMemoryConnection());
36+
37+
var project = new ElasticsearchProject { Name = "foo" };
38+
39+
Assert.AreEqual(project.Name, client.Infer.Id<ElasticsearchProject>(project));
40+
}
41+
42+
[Test]
43+
public void MapIdPropertiesForMultipleTypes()
44+
{
45+
var settings = new ConnectionSettings()
46+
.MapIdPropertyFor<ElasticsearchProject>(p => p.LongValue)
47+
.MapIdPropertyFor<Person>(p => p.Id)
48+
.MapIdPropertyFor<Product>(p => p.Name);
49+
50+
var client = new ElasticClient(settings, connection: new InMemoryConnection());
51+
52+
var project = new ElasticsearchProject { LongValue = 1 };
53+
Assert.AreEqual(project.LongValue.ToString(), client.Infer.Id<ElasticsearchProject>(project));
54+
55+
var person = new Person { Id = 2 };
56+
Assert.AreEqual(person.Id.ToString(), client.Infer.Id<Person>(person));
57+
58+
var product = new Product { Name = "foo" };
59+
Assert.AreEqual(product.Name, client.Infer.Id<Product>(product));
60+
}
61+
62+
[Test]
63+
public void IdPropertyNotMapped_IdIsInferred()
64+
{
65+
var settings = new ConnectionSettings();
66+
var client = new ElasticClient(settings, connection: new InMemoryConnection());
67+
var project = new ElasticsearchProject { Id = 123 };
68+
69+
Assert.AreEqual(project.Id.ToString(), client.Infer.Id<ElasticsearchProject>(project));
70+
}
71+
72+
[Test]
73+
public void DifferentIdPropertyMappedTwice_ThrowsException()
74+
{
75+
var e = Assert.Throws<ArgumentException>(() =>
76+
{
77+
var settings = new ConnectionSettings()
78+
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id)
79+
.MapIdPropertyFor<ElasticsearchProject>(p => p.Name);
80+
});
81+
82+
e.Message
83+
.Should()
84+
.Be("Cannot map 'Name' as the id property for type 'ElasticsearchProject': it already has 'Id' mapped.");
85+
}
86+
87+
[Test]
88+
public void SameIdPropertyMappedTwice_DoesNotThrowException()
89+
{
90+
Assert.DoesNotThrow(() =>
91+
{
92+
var settings = new ConnectionSettings()
93+
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id)
94+
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id);
95+
});
96+
}
97+
98+
99+
[ElasticType(IdProperty = "Name")]
100+
public class IdPropertyTestWithAttribute
101+
{
102+
public string Id { get; set; }
103+
public string Name { get; set; }
104+
}
105+
106+
[Test]
107+
public void IdPropertyMapped_And_TypeHasIdPropertyAttribute_MappingTakesPrecedence()
108+
{
109+
var settings = new ConnectionSettings()
110+
.MapIdPropertyFor<IdPropertyTestWithAttribute>(o => o.Id);
111+
112+
var client = new ElasticClient(settings, connection: new InMemoryConnection());
113+
114+
var doc = new IdPropertyTestWithAttribute
115+
{
116+
Id = "should-be-the-id",
117+
Name = "should-not-be-the-id"
118+
};
119+
120+
Assert.AreEqual(doc.Id, client.Infer.Id<IdPropertyTestWithAttribute>(doc));
121+
}
122+
}
123+
}

src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@
249249
<Compile Include="Internals\Exceptions\BadQueryServerExceptionTests.cs" />
250250
<Compile Include="Internals\Inferno\EscapedFormatTests.cs" />
251251
<Compile Include="Internals\Inferno\HostNameWithPathTests.cs" />
252+
<Compile Include="Internals\Inferno\MapIdPropertyForTests.cs" />
252253
<Compile Include="Internals\Inferno\MapPropertyIgnoreTests.cs" />
253254
<Compile Include="Internals\Inferno\MapTypeNamesTests.cs" />
254255
<Compile Include="Internals\Inferno\MapPropertyNamesForTests.cs" />

0 commit comments

Comments
 (0)