使用Linq2Sql上下文强制立即执行IEnumerable查询

时间:2016-01-11 10:12:12

标签: c# performance linq-to-sql ienumerable

我已经阅读了大量关于“可能的多重枚举”问题的帖子。我想我理解延迟执行与立即执行的概念,以及返回接口与具体类型的含义。

因此,鉴于下面的数据访问层方法和测试代码,我试图强制立即执行查询。 ToList()在数据访问层方法中起作用,但在Main方法中不起作用(可能是因为在放置上下文后调用ToList())。投射as ReadOnlyCollection<Item>(或IReadOnlyCollection)也不起作用。

    static void Main(string[] args)
    {
        var foo = GetItems(i => i.SupiCode.Contains("TestCode")).ToList(); // ObjectDisposedException (context)
    }

    private static IEnumerable<Item> GetItems(Func<Item, bool> filter)
    {
        using (var ctx = new RRPClassesDataContext())
        {
            return ctx.Item.Where(filter); //.ToList(); <-- this works
        }
    }

我的目标是防止多次枚举(即多次访问数据库)。根据我的阅读,我不应该修改DAL以满足客户需求。相反,客户应该正确处理返回的IEnumerable。所以我的问题是:

  • 在这种情况下,客户可以强制立即执行(如果是,如何)?
  • DAL应该返回.ToList()和/或修改签名吗?

3 个答案:

答案 0 :(得分:0)

编写此 ctx.Item.Where(过滤器) linq只会创建一个linq to sql查询,只有在调用ToList()时才会执行。 如果您通过List枚举,那么将对DB运行查询,因为您将IEnumerable作为主类返回,而ToList()正在强制查询执行它抛出错误。

是的,您应该通过ToList()强制执行立即执行。

答案 1 :(得分:0)

您遇到的问题是由于您处置对象上下文的位置。一旦你已经离开ToList方法处理它试图执行的对象上下文,你就会调用GetItems。因此,查询针对已处置的上下文执行,您将获得异常。

您可以稍微更改一下代码来验证这一点,如下所示:

static void Main(string[] args)
{
    using (var ctx = new RRPClassesDataContext())
    {
        var foo = GetItems(ctx, i => i.SupiCode.Contains("TestCode"));

        // force execution. context is still open so query works.
        var bar = foo.ToList();
    }
}

private static IEnumerable<Item> GetItems(RRPClassesDataContext ctx, Func<Item, bool> filter)
{
    return ctx.Item.Where(filter);        
}

所以,具体回答你的问题:

  

在这种情况下,客户可以强制立即执行(如果是,如何)?

不,你不能“强迫”消费者(我认为你的意思是“客户”?)来执行查询,除非你自己强制执行查询(通过调用ToList或{{ 1}}等。请注意,可以执行此操作而不违反您的API:

ToArray

这是确保查询不会多次执行的唯一方法。

  

DAL应该返回.ToList()和/或修改签名吗?

这取决于您的应用程序的架构。如果您的DAL将负责打开和关闭数据库连接(即创建/销毁上下文类),那么您别无选择 - 在上下文关闭之前必须强制执行(以避免上下文)处置例外)。

但是,如果您可以保证在方法执行完毕后您的数据库上下文保持活动状态(例如,如果您为每个Web请求共享一个上下文类),那么不,您不一定必须强制执行。

这样做的一种方法是将您的数据上下文类依赖注入您的DAL,例如:

private static IEnumerable<Item> GetItems(Func<Item, bool> filter)
{
    using (var ctx = new RRPClassesDataContext())
    {
        // Forces execution and safely allows the context to be disposed.
        // Still returns an IEnumerable<Item> so the method contract
        // is preserved.
        return ctx.Item.Where(filter).ToList();
    }
}

这种方法的缺点是(正如您所暗示的那样),您不能保证您不会最终执行两次查询(因为它现在由调用程序强制执行查询)。

答案 2 :(得分:0)

如果您不希望从DAL泄漏Context引用,则必须在GetItems方法内执行查询并返回结果。正如你已经通过.ToList做的那样。

在我看来,这也是你要写的事情。你希望查询执行并立即返回结果,创建一个上下文实例是一个非常便宜的操作,所以在执行查询时创建它是一种很好的做法。 GetItem方法签名可以更改为

private static ICollection<Item> GetItems(Func<Item, bool> filter)
    {
        using (var ctx = new RRPClassesDataContext())
        {
            return ctx.Item.Where(filter).ToList(); <-- this works
        }
    }

这也将解决您的“可能的多重枚举”问题。