Question

i'm trying to build a dynamic dbcontext which does not use the DataAnnotation provided by EF.

So in my override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) I generate a dynamic generic Type which has all the Attributes and Type, needed for the 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 });

I found the LinqRuntimeTypeBuilder as an answer to another question and adjusted the code to fit my needs:

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));
    }
}

But the Invocation of HasKey throws an exception:

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)

if I do an static Invoke of this using modelBuilder.Configurations.HasKey('Param_0 => new {LfdVtgNr = Param_0.LfdVtgNr}) it works, but not dynamically.

Of course i could use another way doing this, but i want to understand the error.

I would appreciate any help.

Était-ce utile?

La solution

The problem is that the expression you're creating looks quite different from the one that's normally created from a lambda that returns an anonymous type.

In your case, the body of the expression looks like this:

  • MemberInit
    • New
      • Parameters: empty
      • Members: empty
    • Bindings:
      • Binding: LfdVtgNr = Param_0.LfdVtgNr

But it should look like this:

  • New
    • Parameters: Param_0.LfdVtgNr
    • Members: LfdVtgNr

(Don't let the syntax fool you, creating anonymous type is really converted to just a constructor call, not MemberInit.)

But to make this work, the generated type has to contain such constructor (though it doesn't actually have to do anything). Also, the generated type doesn't have to be generic, so you can remove all code related to gaBuilders.

The important part of the code will now look like this:

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();

With this, you can now change the code that creates the expression to something like:

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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top