有人用过EF的Ief dbcontext 释放Factory接口吗

[ASP.net教程]EF线程唯一与DBSession接口对接
你的位置:
[ASP.net教程]EF线程唯一与DBSession接口对接
EF 是(entity framework)对于数据库上下文dbcontext 启动线程唯一,是为了处理数据时没有脏数据,可以使用工厂来共同创建DbContext&DBSession实例代码: public class DBSession:IDBSession {private DbContext Db { get { //创建线程唯一的ef,DBContextFactory是EF工厂 return DBContextFactory.CreateUnityConText(); } }DBContextFactory的代码:作用判断数据上下文是否存在是数据上下文唯一DbContext dbConText = (DbContext)CallContext.GetData("dbConText"); if (dbConText==null) { dbConText = new OAEntities();//OAEntities()是数据上下文model1.Context.cs中的 //.SetData("dbConText", dbConText);存储数据 CallContext.SetData("dbConText", dbConText); } return dbConT如何实现多表操作一次性保存数据,为了避免占用数据链接池多次链接数据库在DBSession绘画中统一提取Db.SaveChanges() 承装与接口中即可解决这样ef线程唯一也与dbsession接口对接了
、 、 、 、 、& & 通过EF 作为操作数据库的工具有一段时间了,也做了几个相对不大的项目,慢慢的也对EF的使用摸索出来了一些规则,虽然说不是技术难点,但是,我说的是但是,能够提高我们开发效率的棉花糖有时我们还是必须要吃的,因为他确实很甜很甜。现在Ef已经更新到6.1.1了,从原来的5.0 到现在也不过是短短的一年多,所以说Ef的生命力还是很强的。什么 你要我对比一下EF和NHibernate的优缺点,这不是本文的重点,我只说一句,EF侧重代码配置,NHibernate 侧重配置文件配置,但是说哪种好,萝卜白菜 各有所爱吧。
& 刚开始使用EF操作数据表我们是这样使用的,通过在DBContext中添加Dbset&T& 这种形式的属性来实现,但是,我说的是但是,这种方式随着我们的实体Domain越来越多的时候我们不得不一个一个的添加到DbContext中,这不禁让我很苦恼,有没有更好的办法呢?
&为了说明的方便,我建立了一个控制台的程序,毕竟EF和具体的项目类型无关。
class Program
static void Main(string[] args)
TestContext testContext = new TestContext();
///获取数据库表Person中的所有数据 在查询的时候最好加上AsNoTracking 禁止EF跟踪
var personList = testContext.Persons.AsNoTracking().ToList();
public class TestContext : DbContext
private static TestContext _
public static TestContext Instance
if (_instance == null)
_instance = new TestContext();
private string _connectionS
public string ConnectionString
if (string.IsNullOrWhiteSpace(_connectionString))
_connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionS
return _connectionS
_connectionString =
public TestContext()
: base("name=testConn")
_connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionS
public TestContext(string connectionString)
: base(connectionString)
/// &summary&
/// 定义的实体
/// &/summary&
public DbSet&Person& Persons { get; set; }
[Table("Person")]
public class Person
public string Name { get; set; }
public string Age { get; set; }
每次添加实体Domain 都要在DbContext 中添加一个对应的属性,很令人苦恼,并且如果属性为string 类型,在数据库中创建的表的长度为max,当然我们可以修改EF的默认约定来让string 类型在数据表中具有一定的长度。我们有没有更好的方式来控制EF创建的数据表呢,并且要易于维护,让所有同事都可以很轻松的掌握。当然,有一个新大陆被发现了。
我们通过反射来找到所有继承自EntityTypeConfiguration的类,这些类就是EF的映射类,我们把这些映射类添加到EF &configuration中就可以实现我们的功能,说太多,上代码。
class Program
static void Main(string[] args)
TestContext testContext = new TestContext();
///获取数据库表Person中的所有数据 在查询的时候最好加上AsNoTracking 禁止EF跟踪
var personList = testContext.Persons.AsNoTracking().ToList();
public class TestContext : DbContext
private static TestContext _
public static TestContext Instance
if (_instance == null)
_instance = new TestContext();
private string _connectionS
public string ConnectionString
if (string.IsNullOrWhiteSpace(_connectionString))
_connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionS
return _connectionS
_connectionString =
public TestContext()
: base("name=testConn")
_connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionS
public TestContext(string connectionString)
: base(connectionString)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
///DomainMapping
所在的程序集一定要写对,因为目前在当前项目所以是采用的当前正在运行的程序集 如果你的mapping在单独的项目中 记得要加载对应的assembly
///这是重点
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type =& !String.IsNullOrEmpty(type.Namespace))
.Where(type =& type.BaseType != null && type.BaseType.BaseType != null && type.BaseType.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration&&));
foreach (var type in typesToRegister)
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
base.OnModelCreating(modelBuilder);
public class BaseDomain
[Table("Person")]
public class Person
public string ID { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public class PersonMapping : BaseDomainMapping&Person&
public override void Init()
this.ToTable("Person");
this.HasKey(l =& l.ID);
this.Property(l =& l.Name).HasMaxLength(200).IsRequired();//设置Name属性长度为200 并且是必填
this.Property(l =& l.Age).HasMaxLength(200).IsOptional(); //设置Age长度为200 并且可为空
public abstract class BaseDomainMapping&T& : EntityTypeConfiguration&T&
where T : BaseDomain, new()
public BaseDomainMapping()
/// &summary&
/// 初始化代码
/// &/summary&
public virtual void Init()
Console.WriteLine("Init");
这个其实用到了主要两个知识点,一个就是抽象类中定义的virtual方法,在抽象类中进行调用,在继承自抽象类的类中override这个方法,此文为Init,那么子类中的这个方法也会被调用,避免在每个子类中都要写调用Init的方法。
第二个就是,注意OnModelCreating &方法,他找到所有继承自
EntityTypeConfiguration的类,然后添加到Configuration中,也就是我们实现了多个配置,统一添加到EF的配置中,EF在执行的时候会执行所有的配置类,当然包括我们自己定义的映射Mapping。大家可能要说了,这样是可以只写一个映射类就可以,但是我们怎么访问呢?没有了原来的通过属性
TestContext testContext = new TestContext();
///获取数据库表Person中的所有数据 在查询的时候最好加上AsNoTracking 禁止EF跟踪
var personList = testContext.Persons.AsNoTracking().ToList();
通过属性访问很简单方便
,我们需要想办法获取到DbSet 类通过EF访问数据库,现在我们的思路就是通过我们定义的TestContext
,找到我们通过反射注册的所有配置类。
/// &summary&
/// Entity Framework repository
/// &/summary&
public partial class EfRepository&T& : IRepository&T& where T : BaseDomain
private readonly IDbContext _ ///可以认为就是我们定义的TestContext
private IDbSet&T& _
/// &summary&
/// &/summary&
/// &param name="context"&Object context&/param&
public EfRepository(IDbContext context)
this._context =
/// &summary&
/// Get entity by identifier
/// &/summary&
/// &param name="id"&Identifier&/param&
/// &returns&Entity&/returns&
public virtual T GetById(object id)
//see some suggested performance optimization (not tested)
///questions//dbset-find-method-ridiculously-slow-compared-to-singleordefault-on-id/#comment88189
return this.Entities.Find(id);
/// &summary&
/// Insert entity
/// &/summary&
/// &param name="entity"&Entity&/param&
public virtual void Insert(T entity)
if (entity == null)
throw new ArgumentNullException("entity");
this.Entities.Add(entity);
this._context.SaveChanges();
catch (DbEntityValidationException dbEx)
var msg = string.E
foreach (var validationErrors in dbEx.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewL
var fail = new Exception(msg, dbEx);
//Debug.WriteLine(fail.Message, fail);
/// &summary&
/// Update entity
/// &/summary&
/// &param name="entity"&Entity&/param&
public virtual void Update(T entity)
if (entity == null)
throw new ArgumentNullException("entity");
this._context.SaveChanges();
catch (DbEntityValidationException dbEx)
var msg = string.E
foreach (var validationErrors in dbEx.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, dbEx);
//Debug.WriteLine(fail.Message, fail);
/// &summary&
/// Delete entity
/// &/summary&
/// &param name="entity"&Entity&/param&
public virtual void Delete(T entity)
if (entity == null)
throw new ArgumentNullException("entity");
this.Entities.Remove(entity);
this._context.SaveChanges();
catch (DbEntityValidationException dbEx)
var msg = string.E
foreach (var validationErrors in dbEx.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, dbEx);
//Debug.WriteLine(fail.Message, fail);
/// &summary&
/// Gets a table
/// &/summary&
public virtual IQueryable&T& Table
return this.E
/// &summary&
/// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
/// &/summary&
public virtual IQueryable&T& TableNoTracking
return this.Entities.AsNoTracking();
/// &summary&
/// Entities
/// &/summary&
protected virtual IDbSet&T& Entities
if (_entities == null)
_entities = _context.Set&T&();
/// &summary&
/// Repository
/// &/summary&
public partial interface IRepository&T& where T : BaseEntity
/// &summary&
/// Get entity by identifier
/// &/summary&
/// &param name="id"&Identifier&/param&
/// &returns&Entity&/returns&
T GetById(object id);
/// &summary&
/// Insert entity
/// &/summary&
/// &param name="entity"&Entity&/param&
void Insert(T entity);
/// &summary&
/// Update entity
/// &/summary&
/// &param name="entity"&Entity&/param&
void Update(T entity);
/// &summary&
/// Delete entity
/// &/summary&
/// &param name="entity"&Entity&/param&
void Delete(T entity);
/// &summary&
/// Gets a table
/// &/summary&
IQueryable&T& Table { get; }
/// &summary&
/// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
/// &/summary&
IQueryable&T& TableNoTracking { get; }
可以看到这个实现类很简单,就是通过传入我们定义的TestContext,然后通过
_context.Set&T&(); 可以获取到我们定义的DbSet,剩下的就是正常的属性定义一样了。说了那么多,该歇歇了,总结一下思路,就是通过反射注册所有的实现EntityTypeConfiguration的类,(注意实现类 所在的assembly),然后通过context.set&T&()方法获取到我们定义的Domain,就可以进行增删改查等操作。另外还有一点就是,如果只是查询数据,我建议使用AsNoTracking ,禁止EF跟踪,提高一下性能,另外也可以禁止EF缓存查询的数据。
有图有真相。
总结一下:这只是一个简单的实例,在实际的项目中肯定Domain和mapping是分开的,所以注册的时候我们一定要设定好注册的assembly。再次强调。源代码下载:
阅读(...) 评论()(译者注:使用EF开发应用程序的一个难点就在于对其DbContext的生命周期管理,你的管理策略是否能很好的支持上层服务 使用独立事务,使用嵌套事务,并行执行,异步执行等需求? Mehdi El Gueddari对此做了深入研究和优秀的工作并且写了一篇优秀的文章,现在我将其翻译为中文分享给大家。由于原文太长,所以翻译后的文章将分为四篇。你看到的这篇就是是它的第四篇。原文地址:http://mehdi.me/ambient-dbcontext-in-ef6/)
DbContextScope:一个简单的,正确的并且灵活的管理DbContext实例的方式
应当是来看看一种更好地管理这些DbContext实例方式的时候了。
在下面呈现的方式依赖于DbContextScope,它是一个定制的组件,实现了上面说到的环境上下文DbContext方式。DbContextScope和它依赖的相关类的源代码都。
如果你熟悉TransactionScope类,那么你就已经知道如何使用一个DbContextScope了。它们在本质上十分相似&&唯一的不同是DbContextScope创建和管理DbContext实例而非数据库事务。但是就像TransactionScope一样,DbContextScope是基于环境上下文的,可以被嵌套,可以有嵌套行为被禁用,也可以很好地与异步工作流协作。
下面是DbContextScope的接口:
public interface IDbContextScope : IDisposable
void SaveChanges();
Task SaveChangesAsync();
void RefreshEntitiesInParentScope(IEnumerable entities);
Task RefreshEntitiesInParentScopeAsync(IEnumerable entities);
IDbContextCollection DbContexts { get; }
DbContextScope的目的是创建和管理在一个代码块内使用的DbContext实例。一个DbContextScope因此有效的定义了一个业务事务的边界。我将在后面解释为什么我没有将其命名为&工作单元(UnitOfWork)&或者&工作单元范围(UnitOfWorkScope)&&&它们拥有更广泛的使用场景。
你可以直接实例化一个DbContextScope,你也可以依赖IDbContextScopeFactory&&它提供一个方便的方法并使用最常见的配置来创建一个DbContextScope:
public interface IDbContextScopeFactory
IDbContextScope Create(DbContextScopeOption joiningOption = DbContextScopeOption.JoinExisting);
IDbContextReadOnlyScope CreateReadOnly(DbContextScopeOption joiningOption = DbContextScopeOption.JoinExisting);
IDbContextScope CreateWithTransaction(IsolationLevel isolationLevel);
IDbContextReadOnlyScope CreateReadOnlyWithTransaction(IsolationLevel isolationLevel);
IDisposable SuppressAmbientContext();
使用DbContextScope,你的典型服务方法将看起来是这样的:
public void MarkUserAsPremium(Guid userId)
using (var dbContextScope = _dbContextScopeFactory.Create())
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
dbContextScope.SaveChanges();
在一个DbContextScope里面,你可以用两种方式访问scope管理的DbContext实例。你可以像下面这样通过DbContextScope.DbContexts属性获取它们:
public void SomeServiceMethod(Guid userId)
using (var dbContextScope = _dbContextScopeFactory.Create())
var user =
dbContextScope.DbContexts.Get&MyDbContext&.Set&User&.Find(userId);
dbContextScope.SaveChanges();
但那当然也是DbContextScope在方法里面提供的唯一方式。如果你需要在其它地方(比如说仓储类)访问环境上下文DbContext实例,你可以依赖IAmbientDbContextLocator,像下面这样使用:
public class UserRepository : IUserRepository
private readonly IAmbientDbContextLocator _contextL
public UserRepository(IAmbientDbContextLocator contextLocator)
if (contextLocator == null) throw new ArgumentNullException("contextLocator");
_contextLocator = contextL
public User Get(Guid userId)
return _contextLocator.Get&MyDbContext&.Set&User&().Find(userId);
这些DbContext实例是延迟创建的并且DbContextScope跟踪它们以确保在它的范围内任何DbContext派生类只会被创建一个实例。
你将注意到服务方法在整个业务事务范围内不需要知道究竟需要哪种DbContext派生类型。它仅仅需要创建一个DbContextScope并且在其范围内的需要访问数据库的任何组件都能获取到它们需要的DbContext。
嵌套范围(Nesting&Scopes)
一个DbContextScope当然可以被嵌套。让我们假定你已经有一个服务方法,它将用户标记为优质用户,像下面这样:
public void MarkUserAsPremium(Guid userId)
using (var dbContextScope = _dbContextScopeFactory.Create())
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
dbContextScope.SaveChanges();
你正在实现一个新的功能,它要求能在一个业务事务内标记一组用户为优质用户。你可以像下面这样很容易的完成它:
public void MarkGroupOfUsersAsPremium(IEnumerable&Guid& userIds)
using (var dbContextScope = _dbContextScopeFactory.Create())
foreach (var userId in userIds)
// 通过MarkUserAsPremium()创建的子范围将加入我们的范围,
// 因此它能重用我们的DbContext实例,并且对SaveChanges()
// 的调用将没有任何作用。
MarkUserAsPremium(userId);
// 修改都将只有在这儿才能被保存,在顶层范围内,以确保所有的修改
// 以原子的行为要么提交要么回滚。
dbContextScope.SaveChanges();
(当然这是实现这个指定功能的一种非常不高效的方式,但是它说明了如何实现嵌套事务(范围))
这使得创建一个能组合使用多个其它多个服务方法的服务成为可能。
只读范围(Read-only&scopes)
如果一个服务方法是只读的,那么在方法返回之前必须在DbContextScope上调用SaveChanges()方法将是痛苦的,但是如果不调用也有不妥,因为:
&&1.它将使代码审查和维护更困难(你究竟是有意没有调用SaveChanges()还是你忘了调用呢?)
&&2.如果你开启一个显式数据库事务(我们将在后面看到如何这样做),不调用SaveChanges()将导致事务被回滚。数据库监控系统将通常认为事务回滚意味着应用程序错误。造成一种假的回滚不是一个好主意。
DbContextReadOnlyScope用来解决这个问题。下面是它的接口:
public interface IDbContextReadOnlyScope : IDisposable
IDbContextCollection DbContexts { get; }
你可以像下面这样使用它:
public int NumberPremiumUsers()
using (_dbContextScopeFactory.CreateReadOnly())
return _userRepository.GetNumberOfPremiumUsers();
DbContextScope将如你期望的能很好的在异步执行流中工作:
public async Task RandomServiceMethodAsync(Guid userId)
using (var dbContextScope = _dbContextScopeFactory.Create())
var user = await _userRepository.GetAsync(userId);
var orders = await _orderRepository.GetOrdersForUserAsync(userId);
await dbContextScope.SaveChangesAsync();
在上面的例子中,OrderRepository.GetOrdersForUserAsync()方法将能看到并且访问环境上下文DbContext实例&&尽管事实上它是在另一个线程而非DbContextScope最初被创建的线程上被调用。
使这一切成为可能的原因是DbContextScope将它自己存储在CallContext上面的。CallContext通过异步点自动流转。如果你对它背后的工作原理很好奇,Stephen&Toub已经写过。但是如果你想要的只是使用DbContextScope,你只需要知道:它就是能工作。
警告:当你在异步流中使用DbContextScope的时候,有一件事情你必须记住:就像TransactionScope,DbContextScope仅支持在一个单一的逻辑流中使用。
也就是说,如果你尝试在一个DbContextScope范围内开启多个并行任务(比如说创建多个线程或者多个TPL任务),你将陷入大麻烦。这是因为环境上下文DbContextScope将流转到你并行任务使用的所有线程。如果在这些线程中的代码需要使用数据库,它们就都将使用同一个环境上下文DbContext实例,导致多个线程同时使用同一个DbContext实例。
通常,在一个单独的业务事务中并行访问数据库没有什么好处除了增加复杂性。在业务事务中的任何并行操作都应当不要访问数据库。
无论如何,如果你针对需要在一个DbContextScope里面开启一个并行任务(比如说你要通过业务事务的结果独立的执行一些后台处理),你必须在开启并行任务之前禁用环境上下文DbContextScope,你可以像下面这样简单处理:
public void RandomServiceMethod()
using (var dbContextScope = _dbContextScopeFactory.Create())
// 使用环境上下文context执行一些代码
using (_dbContextScopeFactory.SuppressAmbientContext())
// 在这儿,开启的并行任务将不能使用环境上下文context.
// 在这儿,环境上下文将再次变为可用。
// 可以像平常一样执行更多的代码
dbContextScope.SaveChanges();
创建一个非嵌套的DbContextScope
这是一个我期望大部分应用程序永远不需要用到的高级功能。当使用它的时候要认真对待&&因为它能导致一些诡异的问题并且很快导致维护的恶魔。
有些时候,一个服务方法可能需要将变化持久化到底层数据库而不管整个业务事务的结果,就像下面这些情况:
& &1.需要在一个全局的地方记录不应当回滚的信息&&即使业务事务失败。一个典型的例子就是日志或者审计记录。
&&&2.它需要记录一个不能回滚的操作的结果。一个典型的例子就是服务方法和非事务性的远程服务或者API交互。例如,如果你的服务方法使用Facebook&API提交一个状态更新然后在本地数据库记录新创建的状态。这个记录必须被持久化即使整个业务事务因为在调用Facebook&API后出现一些错误而导致的失败。Facebook&API不是事务性的&&你不可能去&回滚&一个Facebook&API调用。那个API调用的结果将永远不会回滚。
在那种情况下,当创建一个新的DbContextScope的时候,你可以传递DbContextScopeOption.ForceCreateNew的值作为joiningOption参数。这将创建一个不会加入环境上下文范围(如果存在一个的话)的DbContextScope:
public void RandomServiceMethod()
using (var dbContextScope = _dbContextScopeFactory.Create(DbContextScopeOption.ForceCreateNew))
// 我们将创建一个新的范围(scope),即使这个服务方法
// 被另一个已经创建了它自己的DbContextScope的服务方
// 法调用。我们将不会加入它。
// 我们的范围将创建新的DbContext实例并且不会重用
// 父范围的DbContext实例。
// 由于我们强制创建了一个新的范围,这个对SaveChanges()
// 的调用将会持久化我们的修改&&不管父范围(如果有的话)
// 是否成功保存了它的变化。
dbContextScope.SaveChanges();
这样处理的最大问题是服务方法将使用独立的DbContext实例而非业务事务中的其它DbContext。为了避免诡异的bug和维护恶魔,下面列出了一些要服从的基本规则:
服务方法返回的持久化实体必须总是依附(Attach)在环境上下文DbContext上
如果你强制创建一个新的DbContextScope而非加入一个已经存在的上下文环境DbContextScope,你的服务方法必须不能返回它在新的范围(scope)创建或者获取的实体。否则将导致巨大的复杂性。
调用你服务方法的客户端代码可能是一个创建了它自己的DbContextScope的服务方法。因此它期望它调用的所有服务方法都使用相同的环境上下文DbContextScope(这是使用环境上下文Context的立足点)。很自然的期望通过你的服务方法返回的实体都依附(attach)在环境上下文DbContext上。
相反,你也可以采取下面两种策略:
&&&&&不要返回持久化实体。这是最容易,最干净的方法。比如,如果你的服务方法创建一个新的领域对象,不要返回它,而是返回它的Id并且让客户端在它自己的DbContext上面加载这个实体(如果客户端真的需要这个实体的话)。
&&&&&如果你无论如何也要返回一个持久化实体的话,切换回环境上下文DbContext,加载实体并将其返回。
在退出时,一个服务方法必须确保对持久化对象的所有修改都已经在父范围中重现
如果你的服务方法强制创建了一个新的DbContextScope并且在这个新的范围里面修改了持久化对象,必须确保在返回的时候父范围(如果存在的话)能&看到&这些修改。
也就是说,如果父范围的DbContext实例已经加载了你修改过的实体在它的一级缓存中(ObjectStateManager),你的服务方法必须刷新这些实体以确保父范围不会使用这些对象的过时版本。
DbContextScope提供了一个快捷方法来帮助处理这个问题:
public void RandomServiceMethod(Guid accountId)
// 强制创建一个新范围(也就是说,我们将使用我们自己
// 的DbContext 实例)
using (var dbContextScope = _dbContextScopeFactory.Create(DbContextScopeOption.ForceCreateNew))
var account = _accountRepository.Get(accountId);
account.Disabled = true;
// 由于我们强制创建了一个新的范围,这将持久化我
// 们的变化到数据库而不管父范围的处理成功与否。
dbContextScope.SaveChanges();
// 如果这个方法的调用者已经加载过account对象到
// 它们的DbContext实例中,它们的版本现在已经变
// 得过时了。它们将看不到这个account已经被禁用
// 并且可能因此执行一些错误的逻辑。
// 因此需要确保我们的调用者的版本要保持更新。
dbContextScope.RefreshEntitiesInParentScope(new[] { account });
为什么命名为DbContextScope而不是UnitOfWork(工作单元)?
我写的DbContextScope的第一个版本确实被命名为UnitOfWork,这可以说是这种类型的组件最常用的名称。
但是当我尝试在现实程序中使用那个UnitOfWork组件的时候,我一直很困惑&&我应该如何使用它和它真的做了什么&&尽管我是那个研究,设计和实现了它的人并且我还对它能做什么以及如何工作都了如指掌。然而,我仍然很困难并且不得不倒退一步去仔细回想这个&unit&of&work&怎样关联我要尝试解决的实际问题:管理我的DbContext实例。
如果即使我&&那个花了很多时间去研究,设计和实现了这个组件的人在尝试使用的时候都变得很困惑的话,要让其他人来容易的使用它&&这恐怕是没什么希望了。
因此我将其重命名为DbContextScope并且突然所有的事情都变得清晰明朗了。
使用UnitOfWork最主要的问题我相信是在应用程序级别的,它通常没有什么意义。在一个更低的层次,比如数据库级别,一个&unit&of&work&是一个非常清晰并且具体的概念。下面是Martin&Fowler对unit&of&work的定义:
&&&&&维护受业务影响的对象列表,并协调变化和并发问题的解决。
在数据库级别,unit&of&work要表达的东西没有二义性。
然而在一个应用程序级别,一个&unit&of&work&是一个非常模糊的概念&&它可能指所有东西,但又可能什么都不是。并且这个&unit&of&work&如何关联到EF是不清晰的&&对于管理DbContext实例的问题,对于我们操作的持久化对象依附到正确的DbContext实例上的问题。
因此,任何开发人员在尝试使用一个&UnitOfWork&的时候都会搜索它的源代码去查看它究竟做了什么。工作单元(unit&of&work)模式的定义太过于模糊以至于在应用程序级别没什么用处。
实际上,对大部分应用程序,一个应用程序级别的&unit&of&work&甚至没有任何意义。许多应用程序在业务事务中不得不使用几个非事务性的服务,比如远程API或者非事务性的遗留组件。这些地方做出的修改不能被回滚。假装这些不存在是反效率的,迷惑的并且甚至更难写出正确的代码。
相反,DbContextScope刚好完成了它需要完成的工作,不多,不少。它没有假装成别的东西。并且我发现这个简单的更名有效的减少了使用这个组件的认知负荷和去验证是否正确的使用了它。
当然,将这个组件命名为DbContextScope就再也不能掩盖你的服务方法正在使用EF的事实。UnitOfWork是一个非常模糊的概念&&它允许抽象在底层使用的持久化机制。从你的服务层中抽象EF是否是一件好事是一个另外争论&&我们在这儿就不深入它了。
直接去看看吧
放在包括了一个demo程序来演示大部分的使用场景
DbContextScope是如何工作的
已经做了很好的注释并且我鼓励你通读它。另外,是必读的&&如果你想要完全理解DbContextScope中的环境上下文context模式是如何实现的话。
EF团队的项目经理,对于用EF开发项目的任何开发人员来说都是必须要去读的。
哪些地方不能创建你的DbContext实例
在现实程序中经常看到的一个使用EF的反模式是将创建和释放DbContext的代码都放到数据访问方法里面(也就是在传统三层架构中的仓储方法里面)。它通常看起来像这样:
public class UserService : IUserService
private readonly IUserRepository _userR
public UserService(IUserRepository userRepository)
if (userRepository == null) throw new ArgumentNullException("userRepository");
_userRepository = userR
public void MarkUserAsPremium(Guid userId)
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
_userRepository.Save(user);
public class UserRepository : IUserRepository
public User Get(Guid userId)
using (var context = new MyDbContext())
return context.Set&User&().Find(userId);
public void Save(User user)
using (var context = new MyDbContext())
// (要么将提供的实体依附在context上,要么从context加载它,
// 并且从提供的实体更新它的字段)
context.SaveChanges();
通过这样处理,你基本上失去了EF通过DbContext提供的每一个功能,包括它的一级缓存,它的标识映射(Identity&map),它的工作单元(unit-of-work),它的变更追踪和延迟加载功能。因为在上面的场景中,对于每一个数据库查询都将创建一个新的DbContext实例并且随后立即就被释放掉,因此阻碍了DbContext实例去跟踪你的整个业务事务范围内的数据的状态。
你有效的将EF简化为一个简单ORM框架:一个将你的对象与它在数据库中的关系表现映射的工具。
这种架构对于一些应用程序是说得通的。如果你工作在这样一个应用程序,无论如何你应当首先问你自己为什么要用EF。如果你要将它作为一个简单ORM框架并且不用它提供的任何主要功能,你可能使用一个轻量级的ORM框架(比如)会更好。因为它将会简化你的代码并且由于没有EF附加功能的开销而提供更好的性能。
阅读(...) 评论()

我要回帖

更多关于 ef dbcontext 多线程 的文章

 

随机推荐