为什么这个 SelectMany 执行多个 SQL 查询而不是单个联接



我有以下扩展方法:

public static decimal? GetValue(this Member member)
{
    return member.Readings.SelectMany(r => r.Measurements).GetLatestValue();
}

GetLatestValue 是另一个仅使用其他 LINQ 扩展的扩展:OrderByWhereSelectFirst

我希望这会执行JOIN查询。相反,当我查看 SQL 事件探查器时,它正在执行单独的查询以选择每个读数的所有测量值。

我从这个问题和这个问题中了解到,如果我传入数据库上下文并使用它,我可以得到一个JOIN,但这对我来说不是一个选择。

这是怎么回事?为什么Readings属性是ICollection,而不是IQueryable?如何在此处获取单个查询,而无需更改扩展方法签名?

这是怎么回事?

您对问题的描述是准确的。

"为什么读数属性是 ICollection,而不是 IQueryable?"

这是实体框架中的设计错误。

如何在此处获取单个查询,而无需更改扩展方法签名?

这是不可能的。您的方法强制计算查询。即使member.Readings IQueryable你仍然会在这里强制评估。

请注意,EF 永远无法远程GetLatestValue到 SQL(我假设它是您的自定义函数)。没有解决方法。EF 无法为任意 C# 函数生成 SQL。

不幸的是,对于您的情况,没有很好的解决方案。您必须重构代码,以便它与实体框架及其限制很好地配合使用。您链接到的帖子与执行此操作相关。

我想出了一个解决方案,允许我按照我希望它工作的方式使用我的调用代码,但有一些技巧:

在我的扩展方法和需要类似查询的任何其他地方,我只需要确保我调用以下代码:

EvilHackyContextUtilities.SetReadingsQueryableHack(member);

这样做是用我自己的ICollection替换member.Readings属性,这也是一个IQueryable。事情的IQueryable方面使用我链接到的其他问题中建议的相同查询。我已经设法使用反射获取ObjectContext,然后将其传递到我的新MyDbContext的构造函数中。我不得不稍微更改.tt源代码以添加一个使用 DbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext) 作为其基础的构造函数。

public static class EvilHackyContextUtilities
{
    private static MyDbContext GetDbContext(object entity)
    {
        var entityWrapper = entity.GetType().GetField("_entityWrapper").GetValue(entity);
        var objectContext = entityWrapper.GetType().GetProperty("Context").GetValue(entityWrapper, null) as ObjectContext;
        return new MyDbContext(objectContext, false);
    }
    public static void SetReadingsQueryableHack(Member entity)
    {
        if (entity.Readings is EvilHackyQueryableCollection<Reading>)
            return;
        IQueryable<Reading> query = GetDbContext(entity).Readings.Where(r => r.MemberID == entity.MemberID);
        entity.Readings = new EvilHackyQueryableCollection<Reading>(entity.Readings, query);
    }
}
internal class EvilHackyQueryableCollection<TEntity> : ICollection<TEntity>, IQueryable<TEntity>
    where TEntity : class
{
    private readonly IQueryable<TEntity> _baseQuery;
    private readonly ICollection<TEntity> _baseCollection;
    public EvilHackyQueryableCollection(ICollection<TEntity> baseCollection, IQueryable<TEntity> baseQuery)
    {
        _baseQuery = baseQuery;
        _baseCollection = baseCollection;
    }
    #region ICollection members
    //All middle-man methods wrapping up the _baseCollection field.
    #endregion
    #region IQueryable members
    //All middle-man methods wrapping up the _baseQuery field.
    #endregion
}

最新更新