I need to use Fluent-nHibernate a开发者_运维百科gainst a table with a composite primary key (Azure Table, primary keys being (PartitionKey,RowKey) and I would like to map them with corresponding properties on the entity (or with a component property, if easier)
my table would look like:
{
PartitionKey PK,
RowKey PK,
[..]
}
and the entity
public class MyRecord
{
public virtual string PartitionKey{get;set;}
public virtual string RowKey{get;set;}
[...]
}
My current projet uses a custom nHibernate Driver targeting AzureTable.
I managed to make it work with ClassMap or XML mappings. Therefore I am sure that the driver is working. Furthermore, the azure table HTTP requests are correct using classmaps or XML declarations.
However I really need conventions, so this isn't an acceptable solution.
Finally, there is always the option to map only RowKey as a PK, even if the Datastore use (PartitionKey,RowKey). It works too, However it's not really satisfying as it introduces an unicity handling mismatch between nHibernate and the underlying datastore.
UPDATE:
I tried to build a custom IIdentityConvention. The IIdentityInstance.Column() method takes into account only the first call. However, if I use reflection to add both columns to the underlying mapping field, the configuration build fails with an XML validation exception (attribute 'class' required)
I got it working today, but it's not pretty. It also doesn't use a convention. As I understand conventions, they're really meant for tweaking things after the main mapping has occurred. Adding mappings I believe is considered out of scope for conventions.
In my project I have a generic automapping-based initialization procedure that knows nothing of types, but has dependency-injected mapping overrides for composite keys. Not exactly your scenario, but it's a similar problem.
The way I got this to work through reflection was to get hold of the appropriate AutoPersistenceModel object. If you have code looking like this:
Fluently.Configure().Mappings(m => ...
The AutoPersistenceModel object would be m.AutoMappings.First()
From here, it's pretty serious reflection work, culminating in a call to a protected method inside FluentNHibernate. Here's the code I'm using:
private void Override(AutoPersistenceModel container,
Type type,
IEnumerable<KeyValuePair<string,string>> compositeKeys)
{
// We need to call container.Override<T>(Action<Automapping<T>> populateMap)
// Through reflection...yikes
var overrideMethod = typeof(AutoPersistenceModel)
.GetMethod("Override")
.MakeGenericMethod(type);
var actionFactoryMethod = typeof(FluentNHibernateInitializer)
.GetMethod("CompositeMapperFactory",
BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(type);
var actionMethod = actionFactoryMethod
.Invoke(this, new object[] { compositeKeys });
overrideMethod.Invoke(container, new object[] {actionMethod});
}
private Action<AutoMapping<T>> CompositeMapperFactory<T>
(IEnumerable<KeyValuePair<string, string>> compositeKeys)
{
return new Action<AutoMapping<T>>(m =>
{
var compositeId = m.CompositeId();
foreach (var kvp in compositeKeys)
compositeId =
AddKeyProperty(
compositeId,
typeof(T).GetProperty(kvp.Key),
kvp.Value);
}
);
}
/// <summary>
/// Uses reflection to invoke private and protected members!
/// </summary>
/// <param name="compositeId"></param>
/// <param name="propertyInfo"></param>
/// <returns></returns>
private CompositeIdentityPart<T> AddKeyProperty<T>
(CompositeIdentityPart<T> compositeId,
PropertyInfo propertyInfo,
string column)
{
var member = FluentNHibernate.MemberExtensions.ToMember(propertyInfo);
var keyPropertyMethod = typeof(CompositeIdentityPart<T>)
.GetMethod("KeyProperty",
BindingFlags.Instance | BindingFlags.NonPublic);
return (CompositeIdentityPart<T>)
keyPropertyMethod
.Invoke(compositeId, new object[] { member, column, null });
}
精彩评论