Выбор в запросе GroupBy с помощью NHibernate с динамическим анонимным объектом - PullRequest
2 голосов
/ 10 мая 2019

Моя основная задача - создать динамическую группу и использовать ее в 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 (выражение-выражение) (опущено выражение)

*

Я сомневаюсь:

  • Я знаю, что NHibernate поддерживает оператор Select с группой по и анонимным типом, но почему он не может поддержать Select, еслиэтот тип был создан с помощью дерева выражений?

1 Ответ

4 голосов
/ 13 мая 2019

Очевидно, что транслятор запросов NHibernate LINQ не поддерживает MemberInitExpression в GroupBy селекторе.

Но почему работает анонимный тип? Потому что хотя выражение 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);
} 
...