1

Я пытаюсь создать динамический dbcontext, который не использует DataAnnotation, предоставляемую EF.Динамический вызов в EntityFramework EntityTypeConfiguration <>. HasKey

Таким образом, в моем переназначении пустоты OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) сгенерировать динамический универсальный тип, который имеет все атрибуты и тип, необходимый для ключа:

Dictionary<Int32, PropertyInfo> dictIndex = new Dictionary<Int32, PropertyInfo>(); 
... 
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(dictIndex.OrderBy(x => x.Key).Select(x => x.Value)); 
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(paramEx, dictIndex.Select(x => x.Value).FirstOrDefault(x => x.Name == p.Name)))).OfType<MemberBinding>(); 

ConstructorInfo ci = dynamicType.GetConstructor(Type.EmptyTypes); 
Expression selector = Expression.Lambda(Expression.MemberInit(Expression.New(ci), bindings), paramEx); 

var HasKey = config.GetType().GetMethod("HasKey").MakeGenericMethod(dynamicType); 
HasKey.Invoke(config, new[] { selector }); 

Я нашел LinqRuntimeTypeBuilder в качестве ответа на другой вопрос и поправил код чтобы соответствовать моим потребностям:

public static class LinqRuntimeTypeBuilder 
{ 
    private static AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();//new AssemblyName() { Name = "DynamicLinqTypes" }; 
    private static ModuleBuilder moduleBuilder = null; 
    private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>(); 

    static LinqRuntimeTypeBuilder() 
    { 
     moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name); 
    } 

    private static string GetTypeKey(Dictionary<string, Type> fields) 
    { 
     //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter 
     string key = string.Empty; 
     key = "<>f__AnonymousType1`1"; 
     foreach (var field in fields) 
      key += field.Key + ";" + field.Value.Name + ";"; 

     return key; 
    } 

    public static Type GetDynamicType(Dictionary<string, Type> fields) 
    { 
     if (null == fields) 
      throw new ArgumentNullException("fields"); 
     if (0 == fields.Count) 
      throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition"); 

     try 
     { 
      Monitor.Enter(builtTypes); 
      string className = GetTypeKey(fields); 

      if (builtTypes.ContainsKey(className)) 
       return builtTypes[className]; 

      TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.NotPublic,typeof(object)); 
      GenericTypeParameterBuilder[] gaBuilders = typeBuilder.DefineGenericParameters(fields.Select(x => "T" + x.Value.Name).ToArray()); 
      int i = 0; 
      foreach (var field in fields) 
       typeBuilder.DefineField(field.Key, gaBuilders[i++], FieldAttributes.Public); 

      builtTypes[className] = typeBuilder.CreateType().MakeGenericType(fields.Select(x => x.Value).ToArray()); 

      return builtTypes[className]; 
     } 
     catch (Exception ex) 
     { 
     } 
     finally 
     { 
      Monitor.Exit(builtTypes); 
     } 

     return null; 
    } 


    private static string GetTypeKey(IEnumerable<PropertyInfo> fields) 
    { 
     return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType)); 
    } 

    public static Type GetDynamicType(IEnumerable<PropertyInfo> fields) 
    { 
     return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType)); 
    } 
} 

Но Призывание HasKey бросает исключение:

System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.InvalidOperationException: The properties expression `'Param_0 => new {LfdVtgNr = Param_0.LfdVtgNr}'` is not valid. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple properties use an anonymous type: C#: 't => new { t.MyProperty1, t.MyProperty2 }' VB.Net: 'Function(t) New With { t.MyProperty1, t.MyProperty2 }'. 
    bei System.Data.Entity.Utilities.ExpressionExtensions.GetSimplePropertyAccessList(LambdaExpression propertyAccessExpression) 
    bei System.Data.Entity.ModelConfiguration.EntityTypeConfiguration`1.HasKey[TKey](Expression`1 keyExpression) 
    --- Ende der internen Ausnahmestapelüberwachung --- 
    bei System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) 
    bei System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) 

если я делаю статический вызов этого с использованием modelBuilder.Configurations.HasKey('Param_0 => new {LfdVtgNr = Param_0.LfdVtgNr}), он работает, но не динамически.

Конечно, я мог бы использовать другой способ, но я хочу понять ошибку.

Буду признателен за любую помощь.

+0

BTW: Есть любой Symbol/sourceerver для EntityFramework тоже? –

ответ

0

Проблема заключается в том, что создаваемое выражение выглядит совсем не так, как обычно создается из лямбды, которая возвращает анонимный тип.

В вашем случае тело выражения выглядит следующим образом:

  • MemberInit
    • Новый
      • Параметры: пустые
      • Пользователей: пустые
    • привязок:
      • Переплет: LfdVtgNr = Param_0.LfdVtgNr

Но это должно выглядеть следующим образом:

  • Новые
    • Параметры: Param_0.LfdVtgNr
    • Пользователей: LfdVtgNr

(Не позволяйте синтаксису обмануть вас, создавая анонимный тип действительно преобразуются в просто вызов конструктора, не MemberInit.)

Но чтобы сделать эту работу, генерируемый типа должен содержать такой конструктор (хотя на самом деле ничего не нужно делать). Кроме того, сгенерированный тип не обязательно должен быть общим, поэтому вы можете удалить весь код, связанный с gaBuilders.

важная часть кода будет выглядеть следующим образом:

foreach (var field in fields) 
    typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public); 

var parameters = fields.ToArray(); 

var ctor = typeBuilder.DefineConstructor(
    MethodAttributes.Public, CallingConventions.Standard, 
    parameters.Select(p => p.Value).ToArray()); 
var ctorIl = ctor.GetILGenerator(); 
ctorIl.Emit(OpCodes.Ret); 

for (int i = 0; i < parameters.Length; i++) 
{ 
    ctor.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].Key); 
} 

builtTypes[className] = typeBuilder.CreateType(); 

С этим, теперь вы можете изменить код, который создает выражение на что-то вроде:

var ci = dynamicType.GetConstructors().Single(); 
var selector = 
    Expression.Lambda(
     Expression.New(
      ci, 
      ci.GetParameters() 
       .Select(
        p => Expression.Property(
         paramEx, dictIndex.Values.Single(x => x.Name == p.Name))), 
      ci.GetParameters().Select(p => dynamicType.GetField(p.Name))), 
     paramEx); 
+0

Большое спасибо. Сейчас все работает отлично. Но почему конструктор не должен назначать параметры для полей? –

+0

Поскольку конструктор никогда не называется. Он используется здесь только как часть выражения, которое проверяется. – svick