我正在尝试构建一个不使用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})
进行静态调用,它可以正常工作,但不能动态。
当然我可以用另一种方式做这件事,但我想了解错误。
我将不胜感激。
答案 0 :(得分:0)
问题在于,您创建的表达式与通常从返回匿名类型的lambda创建的表达式完全不同。
在您的情况下,表达式的主体如下所示:
但它应该是这样的:
(不要让语法欺骗你,创建匿名类型实际上只转换为构造函数调用,而不是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);