动态调用实体框架EntityTypeConfiguration<> .HasKey

时间:2013-12-18 07:10:42

标签: c# entity-framework dynamic entity-framework-6 linq-expressions

我正在尝试构建一个不使用EF提供的DataAnnotation的动态dbcontext。

因此,在我的覆盖void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)中,我生成了一个动态泛型Type,它具有Key所需的所有属性和类型:

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})进行静态调用,它可以正常工作,但不能动态。

当然我可以用另一种方式做这件事,但我想了解错误。

我将不胜感激。

1 个答案:

答案 0 :(得分:0)

问题在于,您创建的表达式与通常从返回匿名类型的lambda创建的表达式完全不同。

在您的情况下,表达式的主体如下所示:

  • MemberInit
      • 参数:empty
      • 成员:空
    • 绑定:
      • 绑定: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);