我已经阅读了大量关于“可能的多重枚举”问题的帖子。我想我理解延迟执行与立即执行的概念,以及返回接口与具体类型的含义。
因此,鉴于下面的数据访问层方法和测试代码,我试图强制立即执行查询。 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
。所以我的问题是:
.ToList()
和/或修改签名吗?答案 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
}
}
这也将解决您的“可能的多重枚举”问题。