使用Linq2Sql上下文强制立即执行IEnumerable查询
本文关键字:执行 IEnumerable 查询 Linq2Sql 上下文 使用 | 更新日期: 2023-09-27 18:29:44
我已经阅读了大量关于"可能的多重枚举"问题的文章。我想我理解延迟执行与立即执行的概念,以及返回接口与具体类型的含义。
因此,给定下面的数据访问层方法和测试代码,我试图强制立即执行查询。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()
和/或修改签名
您看到的问题是由于处理对象上下文的位置造成的。一旦您已经离开了GetItems
方法,就要调用ToList
,该方法将处理它试图执行的对象上下文。因此,查询是针对已处理的上下文执行的,您会得到异常。
您可以通过稍微更改代码来验证这一点,如下所示:
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
或ToArray
等)。请注意,您可以在不违反API:的情况下执行此操作
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();
}
}
这是确保查询不会被多次执行的唯一方法。
DAL是否应返回.ToList()和/或签名进行修改?
这取决于应用程序的体系结构。如果您的DAL将负责打开和关闭数据库连接(即创建/销毁上下文类),则您别无选择-您必须在上下文关闭之前强制执行(以避免上下文处理的异常)。
但是,如果你可以保证你的DB上下文在方法完成执行后保持活动(例如,如果你每个web请求共享一个上下文类),那么就不必强制执行。
这样做的一种方法是将数据上下文类依赖性注入DAL,类似于:
public class ItemDAL
{
private readonly RRPClassesDataContext _dataContext;
public ItemDAL(RRPClassesDataContext dataContext)
{
_dataContext = dataContext;
}
public IQueryable<Item> GetItems(Func<Item, bool> filter)
{
return _dataContext.Items.Where(filter);
}
}
这种方法的缺点是(正如您所暗示的)您不能保证最终不会执行查询两次(因为现在由调用方强制执行查询)。
编写这个ctx.Item.Where(filter)linq只会创建一个linq-to-sql查询,它只会在调用ToList()时执行。如果您通过List进行枚举,那么查询将针对DB运行,因为您将以IEnumerable的形式返回到main类,而ToList()将强制执行查询并抛出错误。
是的,您应该通过ToList()强制立即执行。
如果不希望Context引用从DAL中泄漏,则必须在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
}
}
这也将解决您的"可能的多重枚举"问题。