Skip to content

Add ability to configure id properties in code #1145

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Nest/Domain/Connection/ConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ string IConnectionSettingsValues.DefaultIndex
private ReadOnlyCollection<Func<Type, JsonConverter>> _contractConverters;
ReadOnlyCollection<Func<Type, JsonConverter>> IConnectionSettingsValues.ContractConverters { get { return _contractConverters; } }

private FluentDictionary<Type, string> _idProperties = new FluentDictionary<Type, string>();
FluentDictionary<Type, string> IConnectionSettingsValues.IdProperties { get { return _idProperties; } }

private FluentDictionary<MemberInfo, PropertyMapping> _propertyMappings = new FluentDictionary<MemberInfo, PropertyMapping>();
FluentDictionary<MemberInfo, PropertyMapping> IConnectionSettingsValues.PropertyMappings { get { return _propertyMappings; } }

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

public T MapIdPropertyFor<TDocument>(Expression<Func<TDocument, object>> objectPath)
{
objectPath.ThrowIfNull("objectPath");

var memberInfo = new MemberInfoResolver(this, objectPath);
var propertyName = memberInfo.Members.Single().Name;

if (this._idProperties.ContainsKey(typeof(TDocument)))
{
if (this._idProperties[typeof(TDocument)].Equals(propertyName))
return (T)this;

throw new ArgumentException("Cannot map '{0}' as the id property for type '{1}': it already has '{2}' mapped."
.F(propertyName, typeof(TDocument).Name, this._idProperties[typeof(TDocument)]));
}

this._idProperties.Add(typeof(TDocument), propertyName);

return (T)this;
}

public T MapPropertiesFor<TDocument>(Action<PropertyMappingDescriptor<TDocument>> propertiesSelector)
{
propertiesSelector.ThrowIfNull("propertiesSelector");
Expand Down
1 change: 1 addition & 0 deletions src/Nest/Domain/Connection/IConnectionSettingsValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public interface IConnectionSettingsValues : IConnectionConfigurationValues
ElasticInferrer Inferrer { get; }
FluentDictionary<Type, string> DefaultIndices { get; }
FluentDictionary<Type, string> DefaultTypeNames { get; }
FluentDictionary<Type, string> IdProperties { get; }
FluentDictionary<MemberInfo, PropertyMapping> PropertyMappings { get; }
string DefaultIndex { get; }
Func<string, string> DefaultPropertyNameInferrer { get; }
Expand Down
6 changes: 6 additions & 0 deletions src/Nest/ExposedInternals/ElasticInferrer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ public string IndexNames(IEnumerable<IndexNameMarker> indices)
public string Id<T>(T obj) where T : class
{
if (obj == null) return null;

string idProperty;
this._connectionSettings.IdProperties.TryGetValue(typeof(T), out idProperty);
if (!idProperty.IsNullOrEmpty())
return this.IdResolver.GetIdFor(obj, idProperty);

return this.IdResolver.GetIdFor(obj);
}

Expand Down
17 changes: 17 additions & 0 deletions src/Nest/Resolvers/IdResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ internal static Func<T, object> MakeDelegate<T, U>(MethodInfo @get)
return t => f(t);
}

public string GetIdFor<T>(T @object, string idProperty)
{
try
{
var value = @object
.GetType()
.GetProperty(idProperty)
.GetValue(@object, null);

return value.ToString();
}
catch
{
return null;
}
}

public string GetIdFor<T>(T @object)
{
if (@object == null)
Expand Down
123 changes: 123 additions & 0 deletions src/Tests/Nest.Tests.Unit/Internals/Inferno/MapIdPropertyForTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using FluentAssertions;
using Elasticsearch.Net.Connection;
using Nest.Tests.MockData.Domain;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nest.Tests.Unit.Internals.Inferno
{
[TestFixture]
public class MapIdPropertyForTests : BaseJsonTests
{
[Test]
public void MapNumericIdProperty()
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<ElasticsearchProject>(p => p.LongValue);

var client = new ElasticClient(settings, connection: new InMemoryConnection());

var project = new ElasticsearchProject { LongValue = 123 };

Assert.AreEqual(project.LongValue.ToString(), client.Infer.Id<ElasticsearchProject>(project));
}

[Test]
public void MapStringIdProperty()
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<ElasticsearchProject>(p => p.Name);

var client = new ElasticClient(settings, connection: new InMemoryConnection());

var project = new ElasticsearchProject { Name = "foo" };

Assert.AreEqual(project.Name, client.Infer.Id<ElasticsearchProject>(project));
}

[Test]
public void MapIdPropertiesForMultipleTypes()
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<ElasticsearchProject>(p => p.LongValue)
.MapIdPropertyFor<Person>(p => p.Id)
.MapIdPropertyFor<Product>(p => p.Name);

var client = new ElasticClient(settings, connection: new InMemoryConnection());

var project = new ElasticsearchProject { LongValue = 1 };
Assert.AreEqual(project.LongValue.ToString(), client.Infer.Id<ElasticsearchProject>(project));

var person = new Person { Id = 2 };
Assert.AreEqual(person.Id.ToString(), client.Infer.Id<Person>(person));

var product = new Product { Name = "foo" };
Assert.AreEqual(product.Name, client.Infer.Id<Product>(product));
}

[Test]
public void IdPropertyNotMapped_IdIsInferred()
{
var settings = new ConnectionSettings();
var client = new ElasticClient(settings, connection: new InMemoryConnection());
var project = new ElasticsearchProject { Id = 123 };

Assert.AreEqual(project.Id.ToString(), client.Infer.Id<ElasticsearchProject>(project));
}

[Test]
public void DifferentIdPropertyMappedTwice_ThrowsException()
{
var e = Assert.Throws<ArgumentException>(() =>
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id)
.MapIdPropertyFor<ElasticsearchProject>(p => p.Name);
});

e.Message
.Should()
.Be("Cannot map 'Name' as the id property for type 'ElasticsearchProject': it already has 'Id' mapped.");
}

[Test]
public void SameIdPropertyMappedTwice_DoesNotThrowException()
{
Assert.DoesNotThrow(() =>
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id)
.MapIdPropertyFor<ElasticsearchProject>(p => p.Id);
});
}


[ElasticType(IdProperty = "Name")]
public class IdPropertyTestWithAttribute
{
public string Id { get; set; }
public string Name { get; set; }
}

[Test]
public void IdPropertyMapped_And_TypeHasIdPropertyAttribute_MappingTakesPrecedence()
{
var settings = new ConnectionSettings()
.MapIdPropertyFor<IdPropertyTestWithAttribute>(o => o.Id);

var client = new ElasticClient(settings, connection: new InMemoryConnection());

var doc = new IdPropertyTestWithAttribute
{
Id = "should-be-the-id",
Name = "should-not-be-the-id"
};

Assert.AreEqual(doc.Id, client.Infer.Id<IdPropertyTestWithAttribute>(doc));
}
}
}
1 change: 1 addition & 0 deletions src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
<Compile Include="Internals\Exceptions\BadQueryServerExceptionTests.cs" />
<Compile Include="Internals\Inferno\EscapedFormatTests.cs" />
<Compile Include="Internals\Inferno\HostNameWithPathTests.cs" />
<Compile Include="Internals\Inferno\MapIdPropertyForTests.cs" />
<Compile Include="Internals\Inferno\MapPropertyIgnoreTests.cs" />
<Compile Include="Internals\Inferno\MapTypeNamesTests.cs" />
<Compile Include="Internals\Inferno\MapPropertyNamesForTests.cs" />
Expand Down