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

时间:2014-02-21 09:41:53

标签: c# linq entity-framework entity-framework-6

我有以下扩展方法:

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

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

我希望这会执行JOIN查询。相反,当我查看SQL事件探查器时,它正在执行单独的查询来为每次读取选择所有度量。

我从thisthis question了解到,如果我传入数据库上下文并使用它,我可以获得JOIN,但这对我来说不是一个选项。

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

2 个答案:

答案 0 :(得分:1)

  

这里发生了什么?

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

  

“为什么Readings属性是ICollection,而不是IQueryable?”

这是Entity Framework中的一个设计错误。

  

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

这是不可能的。您的方法强制要求查询查询。即使member.ReadingsIQueryable,您仍然会在此处强制进行评估。

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

不幸的是,你的情况没有很好的解决方案。您将不得不重构您的代码,以便它与Entity Framework及其限制很好地协作。您链接的帖子与此相关。

答案 1 :(得分:0)

我想出了一个解决方案,允许我使用我的调用代码,因为我希望它可以工作,但有一点点黑客:

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

EvilHackyContextUtilities.SetReadingsQueryableHack(member);

这样做会将member.Readings属性替换为我自己的ICollection,同时也是IQueryableIQueryable方面的内容使用与我链接的其他问题中建议的相同查询。我已设法使用反射获取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
}