Jul*_*che 3 entity-framework-core
我不明白为什么这不翻译。这似乎正是这里描述的用例。
LINQ 表达式
DbSet<A>()
.GroupJoin(
inner: DbSet<B>(),
outerKeySelector: a => a.AId,
innerKeySelector: b => b.AId,
resultSelector: (a, bs) => new {
a = a,
bs = bs
})
Run Code Online (Sandbox Code Playgroud)
产生错误:
无法翻译。以可翻译的形式重写查询,或通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用,显式切换到客户端评估。有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038。
产生异常的 LINQ 代码是
DbSet<A>()
.GroupJoin(
inner: DbSet<B>(),
outerKeySelector: a => a.AId,
innerKeySelector: b => b.AId,
resultSelector: (a, bs) => new {
a = a,
bs = bs
})
Run Code Online (Sandbox Code Playgroud)
编辑:也许我误解了文档,这是一个不翻译的例子。
执行类似于以下示例的查询会生成 Blog & IEnumerable 的结果。由于数据库(尤其是关系数据库)无法表示客户端对象的集合,因此 GroupJoin 在许多情况下不会转换为服务器。它要求您从服务器获取所有数据以在没有特殊选择器的情况下执行 GroupJoin(下面的第一个查询)。但是,如果选择器限制选择的数据,那么从服务器获取所有数据可能会导致性能问题(下面的第二个查询)。这就是 EF Core 不翻译 GroupJoin 的原因。
但后来我的问题变成了:如何在不需要导航属性的情况下实现我正在寻找的结果?
Nuc*_*mer 11
EF Core 不允许您GroupJoin在不跟进 a 的情况下执行 aSelectMany以便使列表扁平化。在 SQL 中没有GroupJoin等效的实现,但是GroupJoin/SelectMany相当于内连接或左连接(取决于您是否使用DefaultIfEmpty),因此它可以正常工作:
context.Users.GroupJoin(
context.UserRoles,
u => u.UserId,
r => r.UserId,
(user, roles) => new { user, roles })
//Will not work without this line
.SelectMany(x => x.roles.DefaultIfEmpty(), (x, r) => new { x.user, role = r })
.ToList();
Run Code Online (Sandbox Code Playgroud)
如果您确实希望对结果进行分组(而不是尝试进行左连接),您有以下几种选择:
您可以具体化左连接的结果,然后在内存中对结果进行分组(下面的代码使用LINQ 中的 LEFT OUTER JOINLeftJoin中显示的我的函数):
context.Users.LeftJoin(
context.UserRoles,
u => u.UserId,
r => r.UserId,
(user, roles) => new { user, roles })
.ToList()
.GroupBy(x => x.user, (u, x) => new
{
User = u,
Roles = x.Select(z => z.role).Where(r => r != null).ToList()
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
您可以使用子查询。请注意,EF 足够智能,可以在生成 SQL 时使用左连接:
context.Users.Select(u => new
{
User = u,
Roles = context.UserRoles.Where(r => r.UserId == u.UserId).ToList()
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
如果您更喜欢这种GroupJoin语法,但不想继续调用所有其他函数来展平、具体化,然后重新分组结果,则可以使用我的JoinMany()扩展方法。此方法使用子查询方法,但将其包装在一个看起来与该GroupJoin函数非常相似的通用方法中:
context.Users.JoinMany(
context.UserRoles,
(u, r) => u.UserId == r.UserId,
(user, roles) => new { user, roles })
.ToList();
Run Code Online (Sandbox Code Playgroud)
支持代码:
public static class QueryableExtensions
{
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,
IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
return outer
.GroupJoin(inner, outerKeySelector, innerKeySelector, (o, i) => new { o, i })
.SelectMany(o => o.i.DefaultIfEmpty(), (x, i) => new { x.o, i })
.ApplySelector(x => x.o, x => x.i, resultSelector);
}
public static IQueryable<TResult> JoinMany<TOuter, TInner, TResult>(
this IQueryable<TOuter> outers, IQueryable<TInner> inners,
Expression<Func<TOuter, TInner, bool>> condition,
Expression<Func<TOuter, IEnumerable<TInner>, TResult>> resultSelector)
{
//Use a placeholder "p => true" expression for the sub-query
Expression<Func<TOuter, JoinResult<TOuter, IEnumerable<TInner>>>> joinSelector = o =>
new JoinResult<TOuter, IEnumerable<TInner>> { Outer = o, Inner = inners.Where(p => true) };
//Create the where-clause that will be used for the sub-query
var whereClause = Expression.Lambda<Func<TInner, bool>>(
condition.Body.ReplaceParameter(condition.Parameters[0], joinSelector.Parameters[0]),
condition.Parameters[1]);
//Replace the placeholder expression with our new where clause
joinSelector = Expression.Lambda<Func<TOuter, JoinResult<TOuter, IEnumerable<TInner>>>>(
joinSelector.Body.VisitExpression(node =>
(node is LambdaExpression le && le.Parameters.Count == 1 && le.Parameters[0].Type == typeof(TInner)
&& le.Body is ConstantExpression ce && ce.Value is bool b && b)
? whereClause : null),
joinSelector.Parameters[0]);
return outers.Select(joinSelector).ApplySelector(x => x.Outer, x => x.Inner, resultSelector);
}
private static IQueryable<TResult> ApplySelector<TSource, TOuter, TInner, TResult>(
this IQueryable<TSource> source,
Expression<Func<TSource, TOuter>> outerProperty,
Expression<Func<TSource, TInner>> innerProperty,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
var p = Expression.Parameter(typeof(TSource), $"param_{Guid.NewGuid()}".Replace("-", string.Empty));
Expression body = resultSelector?.Body
.ReplaceParameter(resultSelector.Parameters[0], outerProperty.Body.ReplaceParameter(outerProperty.Parameters[0], p))
.ReplaceParameter(resultSelector.Parameters[1], innerProperty.Body.ReplaceParameter(innerProperty.Parameters[0], p));
var selector = Expression.Lambda<Func<TSource, TResult>>(body, p);
return source.Select(selector);
}
public class JoinResult<TOuter, TInner>
{
public TOuter Outer { get; set; }
public TInner Inner { get; set; }
}
}
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression toReplace, Expression newExpression)
=> new ReplaceParameterExpressionVisitor(toReplace, newExpression).Visit(source);
public static Expression VisitExpression(this Expression source, Func<Expression, Expression> onVisit)
=> new DelegateExpressionVisitor (onVisit).Visit(source);
}
public class DelegateExpressionVisitor : ExpressionVisitor
{
Func<Expression, Expression> OnVisit { get; }
public DelegateExpressionVisitor(Func<Expression, Expression> onVisit)
{
this.OnVisit = onVisit;
}
public override Expression Visit(Expression node)
{
return OnVisit(node) ?? base.Visit(node);
}
}
public class ReplaceParameterExpressionVisitor : ExpressionVisitor
{
public ParameterExpression ToReplace { get; }
public Expression ReplacementExpression { get; }
public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, Expression replacement)
{
this.ToReplace = toReplace;
this.ReplacementExpression = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
=> (node == ToReplace) ? ReplacementExpression : base.VisitParameter(node);
}
Run Code Online (Sandbox Code Playgroud)
链接文档中的解释只是遵循 EF Core 团队的愿景并且很荒谬,因为它当然可以轻松翻译 - 我在这里与团队进行了长时间的讨论Query with GroupBy 或 GroupJoin throws exception #17068并在此处继续Query: Support GroupJoin 当它是最终查询运算符 #19930 时,试图说服他们为什么应该支持它,无论参数如何都没有运气。
重点是(这就是当前的解决方法)它可以像相关子查询 ( SelectMany)一样被处理,它被正确地翻译和处理(即使查询结果形状没有 SQL 等效项。
无论如何,当前状态是“需要设计”(无论这意味着什么),解决方法是将连接替换为相关子查询(这是 EF Core 在查询转换期间“扩展”集合导航属性时在内部使用的)。
在你的情况下,更换
join b in ctx.Bs on a.aId equals b.aId into bs
Run Code Online (Sandbox Code Playgroud)
和
let bs = ctx.Bs.Where(b => a.aId == b.aId)
Run Code Online (Sandbox Code Playgroud)
但是,我强烈建议添加和使用导航属性。不知道为什么你“不能使用”它们,在不投影实体的 LINQ to Entities 中,它们只为关系提供元数据,从而自动生成必要的连接。通过不定义它们,您只会给自己带来不必要的限制(除了 EF Core 限制/错误之外)。一般来说,EF Core 在使用导航属性而不是手动连接时效果更好并支持更多功能。
| 归档时间: |
|
| 查看次数: |
534 次 |
| 最近记录: |