【问题标题】:LINQ statement is not translatableLINQ 语句不可翻译
【发布时间】:2020-05-14 15:09:05
【问题描述】:

我有以下包含 LINQ 语句的代码:

public async Task<HashSet<long>> GetMembersRecursive(IEnumerable<long> groupIds)
{
    var containsGroupId = InExpression<Group>("Id", groupIds);
    var containsParentId = InExpression<RecursiveGroupModel>("ParentId", groupIds);

    var groupIdsArray = groupIds as long[] ?? groupIds.ToArray();
    return new HashSet<long>(await MyContext
        .Groups
        .Where(containsGroupId)
        .Select(a => new
        {
            Members = MyContext
                .ViewWithRecursiveGroups
                .Where(containsParentId)
                .SelectMany(c => c.Group.Members)
                .Union(a.Members)
                .Where(b => !b.User.IsActive)
        })
        .SelectMany(a => a.Members.Select(b => b.MemberId))
        .Distinct()
        .ToListAsync());
}

private static Expression<Func<T, bool>> InExpression<T>(string propertyName, IEnumerable<long> array)
{
    var p = Expression.Parameter(typeof(T), "x");
    var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
        .MakeGenericMethod(typeof(long));
    var property = Expression.PropertyOrField(p, propertyName);
    var body = Expression.Call(
        contains
        , Expression.Constant(array)
        , property
    );

    return Expression.Lambda<Func<T, bool>>(body, p);
}

我收到的错误是:

Microsoft.EntityFrameworkCore: Processing of the LINQ expression 'DbSet<RecursiveGroupModel>
     .Where(b => __groupIdsArray_1
         .Contains(b.ParentId))
     .SelectMany(c => c.Group.GroupMembers)
     .Union((MaterializeCollectionNavigation(
         navigation: Navigation: Group.GroupMembers,
         subquery: (NavigationExpansionExpression
             Source: DbSet<GroupMember>
                 .Where(l0 => EF.Property<Nullable<long>>(l, "Id") != null && EF.Property<Nullable<long>>(l, "Id") == EF.Property<Nullable<long>>(l0, "GroupId1"))
             PendingSelector: l0 => (NavigationTreeExpression
                 Value: (EntityReference: GroupMember)
                 Expression: l0)
         )
             .Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
                 Value: (EntityReference: Group)
                 Expression: l), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
                 Value: (EntityReference: Group)
                 Expression: l), "Id") == EF.Property<Nullable<long>>(i, "GroupId1"))))' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.

观点:

CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
     WITH RecursiveGroups (GroupId, ParentId) AS
    (
        SELECT Id, ParentId
        FROM Group
        WHERE ParentId IS NOT NULL
        UNION ALL
        SELECT Group.Id, t.ParentId
        FROM GroupTree t
        JOIN Group ON t.GroupId = Group.ParentId
    )

    SELECT * FROM RecursiveGroups

如果某些变量名称不匹配,请提前道歉-我必须在发布前进行清理。

我知道它无法将代码转换为 SQL,因此它要求我尽早枚举或重写,以便它可以翻译。我已经厌倦了重新排列查询并将其分解为更小的查询,但递归视图上的 SelectMany 似乎无法转换为 SQL。

有没有办法让这个在数据库中工作?还是我完全走错了路?

【问题讨论】:

  • 你为什么要定义你自己的 InExpression?! .Where(c=&gt; groupIds.Contains(c)); 怎么样

标签: c# linq ef-core-3.1


【解决方案1】:

作为替代方案,您可以使用原始 sql 查询。在实体框架代码中,我们需要为该类定义一个 POCO 类和一个 DbSet。在您的情况下,您需要定义一些 YourClass:

public DbQuery<YourClass> YourClasses { get; set; }

和要执行的代码:

var result = context.YourClasses.FromSql("YOURSQL_SCRIPT").ToList();
var asyncresult = await context.YourClasses.FromSql("YOURSQL_SCRIPT").ToListAsync();

【讨论】:

  • 回退到原始 sql 在任何复杂的范围内都不起作用 - 您最终会将大部分 LINQ 代码重写为原始 SQL,这会带来所有严重的缺点。
  • @TomTom 我建议使用原始 sql 查询来避免等待修复所有错误的时间。看起来 OP 正在编写新代码。
【解决方案2】:

是的,欢迎来到 EfCore 3.1 的精彩世界,您所能做的就是“Hello world”。

您的查询存在各种“问题”,因为 EfCore 并没有真正进行 LINQ 处理,除了超级简单的情况。

.Union(a.Members)

无法翻译为运行服务器端并且未启用客户端处理。您唯一的选择是:

  • 强制服务器执行两个部分(使用 AsEnumerable),然后在客户端执行联合。仅当您不将其用作较大语句的一部分(即相交)时才有效,否则它是“将所有数据拉到客户端”的时间,这是不好的。

在当前时间点,我只能建议您放弃 EfCore 并使用 EntityFramework - 根据框架 3.1 - 再次可用。或者使用实体框架经典,它是一个在 netstandard 2.0 上运行并具有全局查询过滤器的端口(这是我喜欢的 EfCore 的一个功能)。最后,这就是我目前要做的,因为 - 好吧 - “更好但没有任何功能且无法正常工作”对我来说并没有削减它。

目前尚不清楚 EfCore 是否会被扩展(他们似乎不认为这是一个修复)以处理除了最基本的 LINQ 语句(有时甚至不是那些)之外的任何内容 - 3.1 中的很多变化非常令人沮丧。

您可以将其移动到视图等中 - 但您可能很快就会发现 EfCore 有更多限制,并且维护所有视图也变得相当有倾向性。我遇到了严重的问题,即使在最简单的情况下,我也不能在任何投影前放置任何条件。甚至简单的错误也会被评论为“我们不愿意改变管道,请等待 11 月的版本 5”。例子? https://github.com/dotnet/efcore/issues/15279.

【讨论】:

    【解决方案3】:

    如果你想将此视图转换为 Linq...


    CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
         WITH RecursiveGroups (GroupId, ParentId) AS
        (
            SELECT Id, ParentId
            FROM Group
            WHERE ParentId IS NOT NULL
            UNION ALL
            SELECT Group.Id, t.ParentId
            FROM GroupTree t
            JOIN Group ON t.GroupId = Group.ParentId
        )
    

    var data1 = db.Group.where(x=>x.ParentId != nul)
                .Select(x=>new {x.Id, x.ParentId})
                .Tolist()
    
    var data2 = (from g in db.Groups
                join gt in db.GroupTree on g.ParentId equals gt.GroupId
                select new { d.Id, ParentId })
                .ToList();
    

    创建一个重新排列数据的类,并让查询返回为已知类型的列表和 只需合并两个列表。

    linqpad 是一个非常有用的工具,可以帮助您学习如何创建 linq,从而为您提供所需的 sql。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多