上面领域层UserService中的代码和我们上一篇中的代码是一样的,netfocus兄提出来一个问题“是不是把user对象加入到repository中就算完成注册了?” 现在看来,如果代码这样写,好像就已经完成了注册的功能。 但是如果真这样写,我又觉得问题更大,也就是为什么我会在上篇的未必留下那个问题,“Domain -> Repository -> Database” 和“BLL -> Dal -> Database” 有区别么?撇开这个问题不说,看看我们上面的EfRepository有没有什么问题? 好用么?现在好像没有办法使用事务啊!带着这个问题我们来看看Unit Of Work能怎么帮我们。
Unit Of Work 与 Repository
我们EfRepository的实现中,每一次Insert/Update/Delete操作被执行之后,变更就会立即同步到数据库中去。第一,我们没有为多个操作添加一个事务的能力;第二,这会为我们带来性能上的损失。而Unit Of Work模式正好解决了我们的问题,下面是Martin Fowler 对于该模式的解释:
“A Unit of Work keep track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that need to be done to alter the database as a result of your work.”
Unit of Work负责跟踪所有业务事务过程中数据库的变更。当事务完成之后,它找出需要处理的变更,并更新数据库。
正如我们大家一直讨论的那样,在EF中,DBContext它本身就已经是一个Unit Of Work的模式,因为上面说的功能它都有。那我们有必要自己再给它包上一层吗?我的答案是肯定的,这个和我们为Repository建立接口是一样的,EF中的IDbSet就是一个Repository模式,但是他们都是EF里面的东西,如果哪天我们换成NHibernate了,我们不可能为了这一个接口和基类把EF这个dll也加进来是么? 我们要做的并不多,因为DbContext.SaveChanges它本身就是有事务的,所以我们只需要创建一个带有SaveChanges的接口就可以了。
// IUnitOfWork.cs
1 namespace RepositoryAndEf.Core.Data
2 {
3 public interface IUnitOfWork : IDisposable
4 {
5 int SaveChanges();
6 }
7 }
接着就是让我们的Context,继承DbContex和我们上面的接口。
1 namespace RepositoryAndEf.Data
2 {
3 public class RepositoryAndEfContext : DbContext, IUnitOfWork
4 {
5 public RepositoryAndEfContext() { }
6
7 public RepositoryAndEfContext(string nameOrConnectionString)
8 : base(nameOrConnectionString)
9 {
10 Configuration.LazyLoadingEnabled = true;
11 }
12
13 protected override void OnModelCreating(DbModelBuilder modelBuilder)
14 {
15 var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
16 .Where(type => !String.IsNullOrEmpty(type.Namespace))
17 .Where(type => type.BaseType != null
18 && type.BaseType.IsGenericType
19 && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
20
21 foreach (var type in typesToRegister)
22 {
23 dynamic configurationInstance = Activator.CreateInstance(type);
24 modelBuilder.Configurations.Add(configurationInstance);
25 }
26 //...or do it manually below. For example,
27 //modelBuilder.Configurations.Add(new LanguageMap());
28
29 base.OnModelCreating(modelBuilder);
30 }
31 }
32 }
哦,对了,别忘了把Repository里面的SaveChanges方法去掉。
1 namespace RepositoryAndEf.Data
2 {
3 public class EfRepository<T> : IRepository<T> where T : BaseEntity
4 {
5 private DbContext _context;
6
7 public EfRepository(IUnitOfWork uow)
8 {
9 if (uow == null)
10 {
11 throw new ArgumentNullException("uow");
12 }
13 _context = uow as DbContext;
14 }
15
16 public T GetById(Guid id)
17 {
18 return _context.Set<T>().Find(id);
19 }
20
21 public bool Insert(T entity)
22 {
23 _context.Set<T>().Add(entity);
24 return true;
25 }
26
27 public bool Update(T entity)
28 {
29 _context.Set<T>().Attach(entity);
30 _context.Entry<T>(entity).State = EntityState.Modified;
31 return true;
32 }
33
34 public bool Delete(T entity)
35 {
36 _context.Set<T>().Remove(entity);
37 return true;
38 }
39
40
41 public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
42 {
43 return _context.Set<T>().Where(predicate).ToList();
44 }
45 }
46 }
那么我们应用层的UserService就可以这样写了。
namespace RepositoryAndEf.Service
{
public class UserService : IUserService
{
private IRepository<User> _userRepository;
private IUnitOfWork _uow =
EngineContext.Current.Resolve<IUnitOfWork>();
public UserService(IRepository<User> userRepository)
{
_userRepository = userRepository;
}
public User Register(string email, string name, string password)
{
var domainUserService = new Domain.UserService(_userRepository);
var user = domainUserService.Register(email, name, password);
// 在调用SaveChnages()之前,做其它的更新操作
// 它们会一起在同一个事务中执行。
_uow.SaveChanges();
return user;
}
}
}
如果光看这段代码有没有觉得很奇怪?没有任何对_userRepository的操作,就做了SaveChanges,因为我们在领域服务里面就已经把新创建的用户实体放到那个userRepository中去了。我想这个问题@田园的蟋蟀纠结过很久:) ,也就是领域服务那里面持有repository的引用,它可以自己将要更新的实体添加到repository中,但是如果对于一些不涉及到领域服务的操作,那这一点就需要在应用层来做了,比如添加商品到购物车的操作。
// 应用层ShoppingCartService.cs
1 namespace RepositoryAndEf.Service
2 {
3 public class ShoppingCartService : IShoppingCartService
4 {
5 private IRepository<ShoppingCart> _shoppingCartRepository;
6 private IRepository<Product> _productRepository;
7 private IUnitOfWork _uow;
8
9 public ShoppingCartService(IUnitOfWork uow,
10 IRepository<ShoppingCart> shoppingCartRepository,
11 IRepository<Product> productRepository)
12 {
13 _uow = uow;
14 _shoppingCartRepository = shoppingCartRepository;
15 _productRepository = productRepository;
16 }
17
18 public ShoppingCart AddToCart(Guid cartId,
19 Guid productId,
20 int quantity)
21 {
22 var cart = _shoppingCartRepository.GetById(cartId);
23 var product = _productRepository.GetById(productId);
24 cart.AddItem(product, quantity);
25
26 _shoppingCartRepository.Update(cart);
27 _uow.SaveChanges();
28 return cart;
29 }
30 }
31 }