i am sorry to not find a better Headline. I am trying to extend a EF4 CTP5 Model via MEF Modules: The Idea is to specifiy some basic entities. Those basic entities are located next to my Context class in my solutions Model assembly.
There will be for example an entity called Variable. Variable is a very general entity and i want some modules of the application to specify Special Variable Entities witch provide more detailed properties, but they should be stored in the same table (TPH - Table per Hierarchy).
To do so, i specified an interface IModelContextExtension
public interface IModelContextExtension
{
void OnModelCreating(IModelBuilderFacade modelBuilder);
}
Each module which wants to make usage of custom variables must export a class which implements this interface. In the OnModelCreating method of the model, i loop each registered module, and call the OnModelCreating method of that module. It then can call e.g. "RegisterVariableType" on the provided IModelBuilderFacade, to announce a Variable-Derived-Type (e.g. MySpecialVariable2).
** The interesting part: ** The RegisterVariableType method seems to work very good, except if the Variable-Derived-Type is located in another (MEF-Loaded) assembly. If i register a Variable from another module, than the complete mapping seems to be damaged. Because, when i now try to add a Variable to its Repository, it crashs during the add saying: "Sequence contains no elements". If i remove the type from the loaded module, it works like expected.
I will post the IRepository stuff, if someone is interested, but i am sure, thats not the problem..
Here the OnModelCreating method from my context (derived from DbContext) class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var modelBuilderFacade = new ModelBuilderFacade(modelBuilder);
modelBuilder.Entity<Variable>().HasKey(x => new { x.Id });
////////////////////////////////////
// register derived VariableTypes
modelBuilderFacade.RegisterVariableType(typeof(MySpecialCyclicVariable));
modelBuilderFacade.RegisterVariableType(typeof(MyVerySpecialVariable));
//modelBuilder.Entity<Variable>().HasKey(x => new { x.Id }); modelBuilder.Entity<Variable>()
// .Map<MySpecialVariabe>(m => m.Requires(DiscriminatorColumn).HasValue(typeof(MySpecialVariabe).Name))
// .Map<MyVerySpecialVariable>(m => m.Requires(DiscriminatorColumn).HasValue(typeof(MyVerySpecialVariable).Name))
// .ToTable(VariableTable);
if (ModelExtensions != null)
{
foreach (var modelContextExtension in ModelExtensions)
{
modelContextExtension.OnModelCreating(modelBuilderFacade);
}
}
Map<Variable>(modelBuilder, modelBuilderFacade.VariableTypes, VariableTable);
////////////////////////////////////
}
The RegisterVariableType function adds the (Variable derived) types to an IEnumerable stored in the modelBuilderFacade.
After the types are added, i call my own Map function to do the TPH Mapping.
[Export(typeof(IModelContextExtension))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class Context : IModelContextExtension
{
public void OnModelCreating(IModelBuilderFacade modelBuilder)
{
modelBuilder.RegisterVariableType(typeof开发者_如何学Go(MyVerySpecialVariable2));
}
}
Here the Map function:
private static void Map<T>(ModelBuilder modelBuilder, IEnumerable<Type> types, string table) where T : class
{
var entityTypeConfigurarion = modelBuilder.Entity<T>();
foreach (var variableType in types)
{
if (!typeof(T).IsAssignableFrom(variableType))
{
throw new InvalidOperationException(string.Format("Cannot map type '{0}' to type {1}", variableType, typeof(T)));
}
// #1: Get the generic Map method of the EntityTypeConfiguration<T>
MethodInfo genericMapMethod = GetGenericEntityTypeConfigurationMapMethod<T>(variableType);
// #2: Get generic type of RequiredMappingActionFactory
var requiredMappingFactoryType = typeof(RequiredMappingActionFactory<>).MakeGenericType(variableType);
// #3 get the action from generic mapping factory
var action = requiredMappingFactoryType.GetProperty("RequiredMappingAction").GetValue(null, null);
entityTypeConfigurarion =
genericMapMethod.Invoke(
entityTypeConfigurarion,
BindingFlags.Public | BindingFlags.Instance,
null,
new [] { /* and the */ action /* goes here */ },
null) as EntityTypeConfiguration<T>;
}
if (entityTypeConfigurarion == null)
{
throw new CompositionException("Something went terrible wrong!");
}
entityTypeConfigurarion.ToTable(table);
}
private static MethodInfo GetGenericEntityTypeConfigurationMapMethod<T>(Type variableType) where T : class
{
var mapMethod =
typeof(EntityTypeConfiguration<T>).GetMethods().Where(
mi => mi.Name == "Map" && mi.IsGenericMethodDefinition).FirstOrDefault();
return mapMethod.MakeGenericMethod(variableType);
}
and here the RequiredMappingActionFactory
internal static class RequiredMappingActionFactory<T> where T : class
{
public static string DiscriminatorColumn = "Discriminator";
public static Action<EntityMappingConfiguration<T>> RequiredMappingAction { get { return RequiredAction; } }
public static void RequiredAction(EntityMappingConfiguration<T> configuration)
{
configuration.Requires(DiscriminatorColumn).HasValue(typeof(T).Name);
}
}
hopefully someone can help me, best regards,
cherio, Chris
Entity Framework 4.1 RC is released! I tried it and i succeeded :-)
http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-release-candidate-available.aspx
See here, the custom Mapping function, which allowes to dynamically add TPH Mappings.
protected void MapEntity(
DbModelBuilder modelBuilder, Type entityType, string toTable, string discriminatorColumn = "Discriminator")
{
var method =
GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(
mi => mi.Name.StartsWith("MapEntity") && mi.IsGenericMethodDefinition).FirstOrDefault();
var genericMethod = method.MakeGenericMethod(entityType);
genericMethod.Invoke(this, new object[] { modelBuilder, toTable, discriminatorColumn });
}
protected void MapEntity<T>(
DbModelBuilder modelBuilder, string toTable, string discriminatorColumn = "Discriminator")
where T : class, IEntity
{
var config = modelBuilder.Entity<T>().Map(
entity =>
{
entity.MapInheritedProperties();
entity.Requires(discriminatorColumn).HasValue(typeof(T).FullName).IsOptional();
});
config.ToTable(toTable);
}
and the usage example:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
MapEntity<Variable>(modelBuilder, toTable: "Variables");
MapEntity<Variable2>(modelBuilder, toTable: "Variables");
foreach (var entityType in ModelExtensions.SelectMany(modelExtension => modelExtension.IntroduceModelEntities()))
{
MapEntity(modelBuilder, entityType, toTable: "Variables");
}
}
cheers, Chris
精彩评论