Правильная коллекция в ConstantExpression для EF Core - PullRequest
1 голос
/ 08 марта 2020

Я пытаюсь реализовать свой собственный сериализатор / десериализатор Expression для передачи его через службы (я хочу реализовать свою собственную конечную точку для службы EF Core). Итак, теперь у меня есть проблемы с коллекциями в LambdaExpressions. Например,

var dataQuery = testDb.Users.Include(e => e.EmployeeInfo).Include(f => f.Notifications).Where(s => tstList.Contains(s.Id)).Select(e => e.FullName);
var tstEspressionBase = dataQuery.Expression;
var tstEspression = new ReflectionLocalValculationVisitor().Visit(tstEspressionBase);

здесь

public class ReflectionLocalValculationVisitor : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression memberExpression)
    {
        var expression = Visit(memberExpression.Expression);

        if (expression is ConstantExpression)
        {
            object container = ((ConstantExpression)expression).Value;
            var member = memberExpression.Member;
            if (member is FieldInfo)
            {
                object value = ((FieldInfo)member).GetValue(container);
                return Expression.Constant(value);
            }
            if (member is PropertyInfo)
            {
                object value = ((PropertyInfo)member).GetValue(container, null);
                return Expression.Constant(value);
            }
        }
        return base.VisitMember(memberExpression);
    }
}

var tstList = new List<Guid>()
{
   new Guid("D45E1A1A-F546-48DB-77BA-08D7775C6A93"),
   new Guid("5B21C782-9B95-48F2-77BD-08D7775C6A93")
};

Выполнение с этим кодом

var providerAsync = testDb.GetService<IAsyncQueryProvider>();
var toListAsyncMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync)).MakeGenericMethod(typeof(string));
var s3 = await toListAsyncMethodInfo.InvokeAsync(null, new object[] { providerAsync.CreateQuery(tstEspression), default(CancellationToken) }).ConfigureAwait(false);

дает мне правильный результат.

Итак, после сериализации / десериализация с Newtonsoft Json У меня проблема со сбором в методе Lambda из Where:

Сообщение об ошибке: выражение LINQ 'DbSet .Where (u => List {d45e1a1a) -f546-48db-77ba-08d7775c6a93, 5b21c782-9b95-48f2-77bd-08d7775c6a93,} .Contains (s.Id)) 'не может быть переведено. Либо переписать запрос в форме, которую можно перевести, либо явно переключиться на оценку клиента, вставив вызов либо AsEnumerable (), AsAsyncEnumerable (), ToList (), либо ToListAsyn c (). См. https://go.microsoft.com/fwlink/?linkid=2101038 для получения дополнительной информации.

Я пытался выполнить эту «рекомендацию», но безрезультатно (см. Код ниже):

var asEnumerableMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.AsEnumerable)).MakeGenericMethod(GenericTypes.Select(e => e.FromNode()).ToArray());
var asEnumerabled = asEnumerableMethod.Invoke(null, new object[] { Value });

здесь Value объект - это List<Guid>, сгенерированный JSON. NET после десериализации. Итак, я сравнил реализованные интерфейсы для Value в ConstantExpression, который представляет List<guid> до сериализации и после десериализации - оба реализуют 8 интерфейсов.

Итак, возможно, у кого-то была такая же проблема.

Спасибо.

PS Я не знаю, почему EF Core дает мне Where(u => ..., а не Where(s => ..., потому что в режиме DebugView для этого выражения я вижу правильное представление Where(s => ....

Давайте посмотрим сериализованное / (десериализованное и восстановленное) выражение (данные из DebugView):

.Call System.Linq.Queryable.Select(
    .Call System.Linq.Queryable.Where(
        .Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include(
            .Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include(
                .Constant<Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[EFCoreDataModel.DataClasses.Users.Base.User]>(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[EFCoreDataModel.DataClasses.Users.Base.User]),
                '(.Lambda #Lambda1<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,EFCoreDataModel.DataClasses.Users.Employ.EmployeeInfo]>))
            ,
            '(.Lambda #Lambda2<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Collections.Generic.ICollection`1[EFCoreDataModel.DataClasses.Notifications.Notification]]>))
        ,
        '(.Lambda #Lambda3<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Boolean]>)),
    '(.Lambda #Lambda4<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.String]>))

.Lambda #Lambda1<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,EFCoreDataModel.DataClasses.Users.Employ.EmployeeInfo]>(EFCoreDataModel.DataClasses.Users.Base.User $e)
{
    $e.EmployeeInfo
}

.Lambda #Lambda2<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Collections.Generic.ICollection`1[EFCoreDataModel.DataClasses.Notifications.Notification]]>(EFCoreDataModel.DataClasses.Users.Base.User $f)
{
    $f.Notifications
}

.Lambda #Lambda3<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Boolean]>(EFCoreDataModel.DataClasses.Users.Base.User $s)
{
    .Call .Constant<System.Collections.Generic.List`1[System.Guid]>(System.Collections.Generic.List`1[System.Guid]).Contains($s.Id)
}

.Lambda #Lambda4<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.String]>(EFCoreDataModel.DataClasses.Users.Base.User $e)
{
    $e.FullName
}

Исходное выражение (из DebugView):

.Call System.Linq.Queryable.Select(
    .Call System.Linq.Queryable.Where(
        .Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include(
            .Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include(
                .Constant<Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[EFCoreDataModel.DataClasses.Users.Base.User]>(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[EFCoreDataModel.DataClasses.Users.Base.User]),
                '(.Lambda #Lambda1<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,EFCoreDataModel.DataClasses.Users.Employ.EmployeeInfo]>))
            ,
            '(.Lambda #Lambda2<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Collections.Generic.ICollection`1[EFCoreDataModel.DataClasses.Notifications.Notification]]>))
        ,
        '(.Lambda #Lambda3<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Boolean]>)),
    '(.Lambda #Lambda4<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.String]>))

.Lambda #Lambda1<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,EFCoreDataModel.DataClasses.Users.Employ.EmployeeInfo]>(EFCoreDataModel.DataClasses.Users.Base.User $e)
{
    $e.EmployeeInfo
}

.Lambda #Lambda2<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Collections.Generic.ICollection`1[EFCoreDataModel.DataClasses.Notifications.Notification]]>(EFCoreDataModel.DataClasses.Users.Base.User $f)
{
    $f.Notifications
}

.Lambda #Lambda3<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.Boolean]>(EFCoreDataModel.DataClasses.Users.Base.User $s)
{
    .Call .Constant<System.Collections.Generic.List`1[System.Guid]>(System.Collections.Generic.List`1[System.Guid]).Contains($s.Id)
}

.Lambda #Lambda4<System.Func`2[EFCoreDataModel.DataClasses.Users.Base.User,System.String]>(EFCoreDataModel.DataClasses.Users.Base.User $e)
{
    $e.FullName
}

Итак, они равны. И сериализованный / десериализованный не имеет параметра u в лямбде, просто 's', как могло бы быть.

1 Ответ

1 голос
/ 09 марта 2020

Скорее всего, проблема вызвана несвязанным параметром лямбда-выражения здесь после десериализации

s => tstList.Contains(s.Id)

Условие на самом деле не имеет значения. И дисплей отладки вводит в заблуждение. s в s => означает , а не тот же экземпляр ParameterExpression, что и s в s.Id. Этого не может быть с C# выражением времени компиляции, но это легко сделать с помощью методов класса Expression. Обратите внимание, что с точки зрения дерева выражений имя параметра не имеет значения, только экземпляр.

Например, следующий фрагмент кода

var param1 = Expression.Parameter(typeof(User), "s");
var param2 = Expression.Parameter(typeof(User), "s");
var body = Expression.Equal(
    Expression.Property(param2, "Id"),
    Expression.Constant(new Guid("D45E1A1A-F546-48DB-77BA-08D7775C6A93"))
);
var predicate = Expression.Lambda<Func<Blog, bool>>(body, param1);

создает допустимое значение ( !?) лямбда-выражение, которое при использовании в запросе LINQ

var test = testDb.Set<User>().Where(predicate).ToList();

сгенерирует исключение, аналогичное рассматриваемому.

После всего сказанного забудьте о коллекциях в константных выражениях и сконцентрируйтесь на лямбда-выражениях, чтобы найти и исправить код, вызывающий расхождение вышеупомянутых параметров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...