我的主要目标是创建一个动态组并在 NHibernate 中使用它。
考虑这个有效的非动态示例:
_repository.Collection<User>().GroupBy(u => new { u.Active }).Select(s => s.Key, Count = s.Count())
现在,我创建一个动态对象来生成 new { u.Active }
动态部分:
private Expression<Func<T, object>> CreateGrouping<T>(IEnumerable<string> by)
{
var dynamicTypeForGroup = GetDynamicTypeForGroup<T>(by);
var sourceItem = Expression.Parameter(typeof(T));
var bindings = dynamicTypeForGroup
.GetFields()
.Select(p => Expression.Bind(p, Expression.PropertyOrField(sourceItem, p.Name)))
.Cast<MemberBinding>()
.ToArray();
return Expression.Lambda<Func<T, object>>(Expression.Convert(
Expression.MemberInit(
Expression.New(dynamicTypeForGroup.GetConstructor(Type.EmptyTypes)),
bindings),
dynamicTypeForGroup),
sourceItem);
}
类型在方法GetDynamicTypeForGroup
中生成然后用 Expression.MemberInit(Expression.New(dynamicTypeForGroup.GetConstructor(Type.EmptyTypes)), bindings)
实例化
这是生成类型的方式:
private Type GetDynamicTypeForGroup<T>(IEnumerable<string> members)
{
var type = typeof(T);
var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.RunAndSave
);
var dynamicModule = dynamicAssembly.DefineDynamicModule(Guid.NewGuid().ToString());
var typeBuilder = dynamicModule.DefineType(Guid.NewGuid().ToString());
var properties = members.Select(prop => type.GetProperty(ObjectExtensions.NormilizePropertyName(prop)))
.Where(prop => prop != null)
.Cast<MemberInfo>();
var fields = properties
.Select(property => typeBuilder.DefineField(
property.Name,
((PropertyInfo)property).PropertyType,
FieldAttributes.Public
)).Cast<FieldInfo>()
.ToArray();
GenerateEquals(typeBuilder, fields);
GenerateGetHashCode(typeBuilder, fields);
return typeBuilder.CreateType();
}
所以,问题
如果我使用 _repository.Collection<User>().GroupBy(u => new { u.Active })
它有效,但如果我添加选择部分 - .Select(s => s.Key, Count = s.Count())
(或任何选择语句)我得到以下 NotSupportedException: MemberInit
System.NotSupportedException: MemberInit em NHibernate.Linq.Visitors.HqlGeneratorExpressionVisitor.VisitExpression(Expression expression) (ommited)
我的疑问是:
- 我知道 NHibernate 支持带有 group by 和匿名类型的 Select 语句,但如果这种类型是使用表达式树创建的,为什么它不能支持 Select?
最佳答案
显然 NHibernate LINQ 查询翻译器不支持 GroupBy
选择器中的 MemberInitExpression
。
但为什么匿名类型有效?因为虽然表达式 new { Active = u.Active }
在语法上看起来 MemberInitExpression
(类初始化器),但实际上它不是!
C# 编译器生成的(NHibernate 支持的)是对构造函数的调用,其参数 ( NewExpression ) 通过 Members 映射到类成员属性 - 以下 Expression.New 的第三个参数过载:
public static NewExpression New (
ConstructorInfo constructor,
IEnumerable<Expression> arguments,
IEnumerable<MemberInfo> members
)
这是问题的解决方案。在您的动态类型构建器中,生成一个参数与字段匹配的构造函数(并在正文中分配相应的字段):
var fields = properties
.Select(property => typeBuilder.DefineField(
property.Name,
((PropertyInfo)property).PropertyType,
FieldAttributes.Public
)).Cast<FieldInfo>()
.ToArray();
GenerateConstructor(typeBuilder, fields); // <--
GenerateEquals(typeBuilder, fields);
GenerateGetHashCode(typeBuilder, fields);
return typeBuilder.CreateType();
然后使用这样的东西:
private Expression<Func<T, object>> CreateGrouping<T>(IEnumerable<string> by)
{
var keyType = GetDynamicTypeForGroup<T>(by);
var sourceItem = Expression.Parameter(typeof(T));
var members = keyType.GetFields();
var arguments = members.Select(m => Expression.PropertyOrField(sourceItem, m.Name));
var constructor = keyType.GetConstructor(members.Select(m => m.FieldType).ToArray());
var newKey = Expression.New(constructor, arguments, members);
return Expression.Lambda<Func<T, object>>(newKey, sourceItem);
}
关于c# - 使用带有动态匿名对象的 NHibernate 在 GroupBy 查询中进行选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56079838/