首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >EF核在ConstantExpression中的正确采集

EF核在ConstantExpression中的正确采集
EN

Stack Overflow用户
提问于 2020-03-08 17:57:57
回答 2查看 296关注 0票数 2

我尝试实现我自己的表达式序列化器/反序列化器来传递它通过服务(我想实现我自己的EF服务端点)。所以,现在我对LambdaExpressions中的集合有问题了。例如,

代码语言:javascript
复制
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);

这里

代码语言:javascript
复制
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")
};

使用此代码执行

代码语言:javascript
复制
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-48 be 77ba-08d7775c6a93,5b21c782-9b95-48f2-77bd-08d7775c6a93,}.Contains(s.Id))无法翻译。要么用可以翻译的表单重写查询,要么通过插入对AsEnumerable()、AsAsyncEnumerable()、ToList()或ToListAsync()的调用,显式地切换到客户端计算。有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038

我试图实现这个“建议”,但没有效果(请参阅下面的代码):

代码语言:javascript
复制
var asEnumerableMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.AsEnumerable)).MakeGenericMethod(GenericTypes.Select(e => e.FromNode()).ToArray());
var asEnumerabled = asEnumerableMethod.Invoke(null, new object[] { Value });

这里,Value对象是由反序列化后的JSON.NET生成的List<Guid>。因此,我比较了ValueConstantExpression中实现的接口,它表示序列化之前的List<guid>和反序列化后的List<guid>,这两个接口都实现了8个接口。

所以也许有人也有同样的问题。

谢谢。

我不知道为什么EF核心给我Where(u => ...而不是Where(s => ...,因为在这个表达式的DebugView模式中,我看到了正确的Where(s => ...表示。

让我们看看序列化/(反序列化和还原的)表达式(来自DebugView的数据):

代码语言:javascript
复制
.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):

代码语言:javascript
复制
.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
}

所以,他们是平等的。并且序列化/反序列化在Lambda中没有u参数,只是's‘。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-03-09 01:50:43

这个问题很可能是由反序列化后的未绑定lambda表达式参数引起的。

代码语言:javascript
复制
s => tstList.Contains(s.Id)

情况并不重要。而调试显示是误导的。s in s =>不是与s.Id中的s相同的ParameterExpression实例。这在C#编译时表达式中是不可能实现的,但是可以使用Expression类方法轻松地完成。注意,从表达式树的角度来看,参数的名称并不重要,只是实例。

例如,下面的代码片段

代码语言:javascript
复制
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);

创建有效的(!?)lambda表达式,当用于LINQ查询时

代码语言:javascript
复制
var test = testDb.Set<User>().Where(predicate).ToList();

会产生类似的异常。

尽管如此,忘记常量表达式中的集合,专注于lambda表达式来查找和修复导致上述参数表达式差异的代码。

票数 2
EN

Stack Overflow用户

发布于 2020-07-28 06:56:00

是的,当您在where表达式中将您自己的自定义集合作为常量使用时,EF Core不能很好地发挥作用。它们在表达式中必须是完全可评估的,即使这样,EF Core也很难正确地翻译它们。也许你想把你的名单压扁?我的意思是,您为每个用户创建一个表达式,将每个项目与其进行比较。

我已经为集合实现了一个小谓词生成器,也许它会对您有所帮助吗?它关注所有的ParameterExpression映射。

代码语言:javascript
复制
var whereExpression = CollectionConstantPredicateBuilder<UserEntity>
    // Here begins the scope of ALL users.
    .CreateFromCollection(tstList)
    // Each expression of one user is combined with OrElse (||)
    .DefinePredicatePerItem(consecutiveItemBinaryExpressionFactory: Expression.OrElse,
        // Pre-Conditions, you may check for null or empty list or you simply configures
        // comparisonValuesBehaviourFlags. Do NOT use method call on 'yourTstList'.
        sourceAndItemPredicate: (user, yourTstList) => true)
    // Here begins the scope of ALL items of 'yourTstList' in ONE user.
    // The resulted expression of the hole collection that belongs to one user
    // is combined with the previous expression from ONE user by AndAlso (&&).
    .ThenCreateFromCollection(parentBinaryExpressionFactory: Expression.AndAlso,
        comparisonValuesFactory: yourTstList => yourTstList,
        // If the collection of the comparison values is null or empty,
        // the boolean expression branch for each each item won't be
        // created. Instead an expression is used that leads to false.
        // Ergo, if 'yourTstList' does not contain the user id, the user
        // won't be queried.
        comparisonValuesBehaviourFlags: ComparisonValuesBehaviourFlags.NullOrEmptyLeadsToFalse)
    // Each expression of one item is combined with OrElse (||).
    // So one user's ID can be the one or the other 'oneTstItem'.
    .DefinePredicatePerItem(Expression.OrElse,
        sourceAndItemPredicate: (user, oneTstItem) => user.Id == oneTstItem)
    .BuildLambdaExpression();

dbContext.Users.AsQueryable().Where(whereExpression).

你可以在我的CollectionConstantPredicateBuilder上找到预发行包(0.1.7-alpha.68) on NuGet

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60590444

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档