实体框架将查询附加到新的DbContext

本文关键字:DbContext 框架 查询 实体 | 更新日期: 2023-09-27 18:21:43

如果查询DbContext的DbSet,则该查询在DbContext被释放之前一直有效。以下情况将导致异常:

IQueryable<Video> allVideos = null;
using (var context = new MyDbContext())
{
    allVideos = context.Videos;
}
var firstVideo = allVideos.first();

显然,使用的DbSet存储在实现IQueryable的返回对象中的某个位置。

但是,MSDN建议(链接)

使用Web应用程序时,请为每个请求使用一个上下文实例。

当然,我可以使用ToList()并将结果作为对象列表返回,但这是不可取的,因为我不知道查询的原因。

例如:假设我的数据库有一个国家,有城市,有街道,有房子,有家庭,有名字的人。

如果有人问IQueryable,那么他可能想搜索居住在英国伦敦唐宁街10号的最年长的人的名字。

如果我用ToList()返回序列,所有的城市、街道、房屋、人员等都会被返回,如果他只需要这一个人的名字,那将是一种浪费。这就是推迟执行林克的好处。

所以我不能返回ToList(),我必须返回IQueryable。

因此,我想做的是打开一个新的DbContext,并以某种方式告诉查询它应该使用新的DbContext:

IQueryable<Video> allVideos = null;
using (var context = new MyDbContext())
{
    allVideos = context.Videos;
}
// do something else
using (var context = new MyDbContext())
{
    // here some code to attach the query to the new context
    var firstVideo = allVideos.first();
}

如何做到这一点?

实体框架将查询附加到新的DbContext

当地的大师正好路过。他向我解释说,我设计中的错误是,我在编写查询时已经使用了DbContext。我的接口应该是这样的,当实际物化请求的对象时,我只需要DbContext。

问题是以下内容的简化版本:

我有一个DbContext,有几个公共DbSet属性。这些属性反映了实际的数据库。为了保护我的数据,我想在抽象数据库层中隐藏实际的数据库实现。我不希望任何人在没有检查这些内容是否正确的情况下对数据库的内容进行更改。

这很容易:只是不要向外部世界公开实际的DbContext,而是公开一个隐藏实际使用的DbContext的facade。这个facade与实际的DbContext通信。

对于大多数返回IQueryable的函数,我需要DbContext来访问DbSets。这就是为什么我想创建一个上下文,构造查询并处理上下文。但由于延迟执行,上下文仍然是必需的。

解决方案

解决方案不是创建自己的上下文,而是让调用者构造DbContext。这个构造的DbContext将是函数的参数之一。在这种情况下,外部用户可以调用我的facade的几个函数来连接查询,甚至可以在DbContext上与他自己的Linq查询混合,而无需创建和处理上下文。就像其他人建议的那样:

  • 调用者创建dbContext
  • 调用者调用了我的几个返回查询的函数,并将dbContext作为参数传递
  • 调用方使用ToList()/ToArray()/First()/Count()等执行查询
  • 调用方处理上下文

更妙的是,dbContext参数用于一个扩展方法:

public static IQueryable<Video> GetObsoleteVideos(this MyDbContext dbContext)
{
    // perform several difficult Linq statements on context
    // that will return all obsolete videos
    return ...
}
public static IQueryable<Video> GetThrillerVideos(this MyDbContext dbContext)
{
     return dbContext.Videos.Where(video => video.Genre == VideoGenre.Thriller);
}

用法:

using (var myContext = new MyDbContext())
{
    var difficultQuery = myContext.GetObsoleteVideos()
        .Where(video => video.Name == ...)
        .GetThrillerVideos()
        .Take(10);
    // Note: the query still deferred, execute it now, before Disposing myContext
    var result = difficultQuery.ToList();
}

通过这种方式(尤其是如果我创建了一个接口),我可以禁止访问我的DbSets。我甚至可以在外部用户没有注意到任何事情的情况下,在内部重新组织我的Db和DbContext。

对象上下文中有一些方法可以做到这一点:

var objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Detach(entity);
objectContext.Attach(entity);

但是,正如MSDN引用的内容所说,每个请求应该使用一个EF上下文实例。这指的是HttpRequest,而不是单个查询。当您在一个请求中执行操作时,不应该在EF上下文周围放置using块,并且应该延长其生存期。对于新的请求,建议不要在请求之间保留状态,而是遵循协议

  1. 再次查询项目并重新加载(在此期间,另一个请求可能已对其进行了修改)
  2. 进行修改
  3. 保存