zoukankan      html  css  js  c++  java
  • EntityFramework之领域驱动设计实践(九)(转)

    http://www.cnblogs.com/daxnet/archive/2010/07/10/1774706.html
    仓储的实现:深入篇

    早在年前的时候就已经在CSAI博客发表了上一篇文章:《仓储的实现:基础篇》。苦于日夜奔波于工作与生活之间,一直没有能够抽空继续探讨仓储的实现细节,也让很多关注EntityFramework和领域驱动设计的朋友们备感失望。

    闲话不多说,现在继续考虑,如何让仓储的操作在相同的事物处理上下文中进行。DDD引入仓储模式,其目的之一就是能够通过仓储隐藏对象持久化的技术 细节,使得领域模型变得更为“纯净”。由此可见,仓储的实现是需要基础结构层的组件支持的,表现为对数据库的操作。在传统的关系型数据库操作中,事务处理 是一个很重要的概念,虽然从目前某些大型项目看,事务处理会降低效率,但它保证了数据的完整性。关系型数据库仍然是目前数据持久化机制的主流,事务处理的 实现还是很有必要的。

    为了迎合仓储模式,就需要对经典的ObjectContext使用方式作一些调整。比如,原本我们可以非常简单地使用using (EntitiesContainer ec = new EntitiesContainer())语句来界定LINQ to Entities的操作范围,并使用ObjectContext的SaveChanges成员方法提交事务,而在引入了仓储的实现中,就不能继续采用这种 经典的使用方式。这让EntityFramework看上去变得很奇怪,也很牵强,我相信很多网友会批评我的做法,因为我把问题复杂化了。

    其实,这应该是关注点不同罢了。关注EntityFramework的开发人员,自然觉得经典的调用方式简单明了,而从DDD的角度看呢?只能把关 注点放在仓储上,而把EntityFramework当成是仓储的一种技术选型(当然从DDD角度讲,我们完全可以不选择 EntityFramework,而去选择其它技术)。所以本文暂且抛开EntityFramework,继续在上文的基础上,讨论仓储的实现。

    前面提到,仓储的实现需要考虑事务处理,而且根据DDD的经验,针对每一个聚合根,都需要有个仓储对其进行持久化以及对象重新组装等操作。为此,我 的想法是,将仓储操作“界定”在某一个事务处理上下文(Context)中,仓储的实例是由Context获得的,这有点像 EntityFramework中ObjectContext与EntityObject的关系那样。由于仓储是来自于transaction context,所以它知道目前处于哪个事务上下文中。我定义的这个transaction context如下:

    隐藏行号 复制代码 Transaction Context
    1. public interface IRepositoryTransactionContext : IDisposable 
    2. { 
    3.     IRepository<TEntity> GetRepository<TEntity>() 
    4.         where TEntity : EntityObject, IAggregateRoot; 
    5.     void BeginTransaction(); 
    6.     void Commit(); 
    7.     void Rollback(); 
    8. } 

    上面第三行代码定义了一个接口方法,这个方法的主要作用就是返回一个针对指定聚合根实体的仓储实例。剩下那三行代码就很明显了,那是标准的transaction操作:启动事务、提交事务以及回滚事务。

    在设计上,可以根据需要,选择合适的技术来实现IRepositoryTransactionContext。我们现在讨论的是 EntityFramework,所以我将给出EntityFramework的具体实现。当然,如果你不选用EntityFramework,而是用 NHibernate实现数据持久化,这样的设计同样能够使你达到目的。以下是基于EntityFramework的实 现:EdmRepositoryTransactionContext的伪代码。

    隐藏行号 复制代码 EdmRepositoryTransactionContext
    1. internal class EdmRepositoryTransactionContext : IRepositoryTransactionContext 
    2. { 
    3.     private ObjectContext objContext; 
    4.     private Dictionary<Type, object> repositoryCache = new Dictionary<Type, object>(); 

    5.     public EdmRepositoryTransactionContext(ObjectContext objContext) 
    6.     { 
    7.         this.objContext = objContext; 
    8.     } 

    9.     #region IRepositoryTransactionContext Members 

    10.     public IRepository<TEntity> GetRepository<TEntity>() where TEntity : EntityObject, IAggregateRoot 
    11.  { 
    12.         if (repositoryCache.ContainsKey(typeof(TEntity))) 
    13.         { 
    14.             return (IRepository<TEntity>)repositoryCache[typeof(TEntity)]; 
    15.         } 
    16.         IRepository<TEntity> repository = new EdmRepository<TEntity>(this.objContext); 
    17.         this.repositoryCache.Add(typeof(TEntity), repository); 
    18.         return repository; 
    19.     } 

    20.     public void BeginTransaction()  
    21.     {  
    22.         // We do not need to begin a transaction here because the object context, 
    23.         // which would handle the transaction, was created and injected into the 
    24.         // constructor by Castle Windsor framework. 
    25.  } 

    26.     public void Commit() 
    27.     { 
    28.         this.objContext.SaveChanges(); 
    29.     } 

    30.     public void Rollback() 
    31.     { 
    32.         // We also do not need to perform the rollback operation because 
    33.         // entity framework will handle this for us, just when the execution 
    34.         // point is stepping out of the using scope. 
    35.  } 

    36.     #endregion 
    37.  
    38.     #region IDisposable Members 

    39.     public void Dispose() 
    40.     { 
    41.         this.repositoryCache.Clear(); 
    42.         this.objContext.Dispose(); 
    43.     } 

    44.     #endregion 
    45. } 

    EdmRepositoryTransactionContext被定义为internal,这个设计是合理的,因为Domain层是不需要知道事 务上下文的具体实现,它将会被IoC/DI容器注入到Domain层中(本系列文章采用Castle Windsor框架)。在EdmRepositoryTransactionContext的构造函数中,它需要EntityFramework的 ObjectContext对象来初始化实例。同样,由于IoC/DI的使用,我们在代码中也是不需要去创建这个ObjectContext的,交给 Castle Windsor就OK了。第13行的GetRepository方法简单地采用了Dictionary对象来实现缓存仓储实例的效果,当然这种做法还有待 改进。

    EdmRepositoryTransactionContext是不需要BeginTransaction的,我们将方法置空,因为EntityFramework的事务会由ObjectContext来管理,同理,Rollback也被置空。

    EdmRepository的实现就比较显而易见了,请参见上文。

    此外,我们还可以针对NHibernate实现仓储模式,只需要实现IRepositoryTransactionContext和IRepository接口即可,比如:

    隐藏行号 复制代码 NHibernateRepositoryTransactionContext实现
    1. internal class NHibernateRepositoryTransactionContext : IRepositoryTransactionContext 
    2. { 
    3.     ITransaction transaction; 
    4.     Dictionary<Type, object> repositoryCache = new Dictionary<Type, object>(); 

    5.     public ISession Session { get { return DatabaseSessionFactory.Instance.Session; } } 

    6.     #region IRepositoryTransactionContext Members 

    7.     public IRepository<TEntity> GetRepository<TEntity>()  
    8.         where TEntity : EntityObject, IAggregateRoot 
    9.  { 
    10.         if (repositoryCache.ContainsKey(typeof(TEntity))) 
    11.         { 
    12.             return (IRepository<TEntity>)repositoryCache[typeof(TEntity)]; 
    13.         } 
    14.         IRepository<TEntity> repository = new NHibernateRepository<TEntity>(this.Session); 
    15.         this.repositoryCache.Add(typeof(TEntity), repository); 
    16.         return repository; 
    17.     } 

    18.     public void BeginTransaction() 
    19.     { 
    20.         transaction = DatabaseSessionFactory.Instance.Session.BeginTransaction(); 
    21.     } 

    22.     public void Commit() 
    23.     { 
    24.         transaction.Commit(); 
    25.     } 

    26.     public void Rollback() 
    27.     { 
    28.         transaction.Rollback(); 
    29.     } 

    30.     #endregion 
    31.  
    32.     #region IDisposable Members 

    33.     public void Dispose() 
    34.     { 
    35.         transaction.Dispose(); 
    36.         ISession dbSession = DatabaseSessionFactory.Instance.Session; 
    37.         if (dbSession != null && dbSession.IsOpen) 
    38.             dbSession.Close(); 
    39.     } 

    40.     #endregion 
    41. } 

    隐藏行号 复制代码 NHibernateRepository实现
    1. internal class NHibernateRepository<TEntity> : IRepository<TEntity> 
    2.     where TEntity : EntityObject, IAggregateRoot 
    3. { 
    4.     ISession session; 

    5.     public NHibernateRepository(ISession session) 
    6.     { 
    7.         this.session = session; 
    8.     } 

    9.     #region IRepository<TEntity> Members 

    10.     public void Add(TEntity entity) 
    11.     { 
    12.         this.session.Save(entity); 
    13.     } 

    14.     public TEntity GetByKey(int id) 
    15.     { 
    16.         return (TEntity)this.session.Get(typeof(TEntity), id); 
    17.     } 

    18.     public IEnumerable<TEntity> FindBySpecification(Func<TEntity, bool> spec) 
    19.     { 
    20.         throw new NotImplementedException(); 
    21.     } 

    22.     public void Remove(TEntity entity) 
    23.     { 
    24.         this.session.Delete(entity); 
    25.     } 

    26.     public void Update(TEntity entity) 
    27.     { 
    28.         this.session.SaveOrUpdate(entity); 
    29.     } 

    30.     #endregion 
    31. } 

    在NHibernateRepositoryTransactionContext中使用了一个DatabaseSessionFactory的 类,该类主要用于管理NHibernate的Session对象,具体实现如下(该实现已被用于我的Apworks应用开发框架原型中):

    隐藏行号 复制代码 DatabaseSessionFactory实现
    1. /// <summary> 
    2. /// Represents the factory singleton for database session. 
    3. /// </summary> 
    4. internal sealed class DatabaseSessionFactory 
    5. { 
    6.     #region Private Static Fields 
    7.     /// <summary> 
    8.     /// The singleton instance of the database session factory. 
    9.  /// </summary> 
    10.  private static readonly DatabaseSessionFactory databaseSessionFactory = new DatabaseSessionFactory(); 
    11.     #endregion 
    12.  
    13.     #region Private Fields 
    14.     /// <summary> 
    15.     /// The session factory instance. 
    16.  /// </summary> 
    17.  private ISessionFactory sessionFactory = null; 
    18.     /// <summary> 
    19.     /// The session instance. 
    20.  /// </summary> 
    21.  private ISession session = null; 
    22.     #endregion 
    23.  
    24.     #region Constructors 
    25.     /// <summary> 
    26.     /// Privately constructs the database session factory instance, configures the 
    27.  /// NHibernate framework by using the assemblies listed in the configured spaces(paths) 
    28.  /// that are decorated by <see cref="EntityVisibleAttribute"/>. 
    29.  /// </summary> 
    30.  private DatabaseSessionFactory() 
    31.     { 
    32.         Configuration nhibernateConfig = new Configuration(); 
    33.         nhibernateConfig.Configure(); 
    34.         nhibernateConfig.AddAssembly(typeof(IAggregateRoot).Assembly); 
    35.         sessionFactory = nhibernateConfig.BuildSessionFactory(); 
    36.     } 
    37.     #endregion 
    38.  
    39.     #region Public Properties 
    40.     /// <summary> 
    41.     /// Gets the singleton instance of the database session factory. 
    42.  /// </summary> 
    43.  public static DatabaseSessionFactory Instance 
    44.     { 
    45.         get 
    46.  { 
    47.             return databaseSessionFactory; 
    48.         } 
    49.     } 

    50.     /// <summary> 
    51.     /// Gets the singleton instance of the session. If the session has not been 
    52.  /// initialized or opened, it will return a newly opened session from the session factory. 
    53.  /// </summary> 
    54.  public ISession Session 
    55.     { 
    56.         get 
    57.  { 
    58.             ISession result = session; 
    59.             if (result != null && result.IsOpen) 
    60.                 return result; 
    61.             return OpenSession(); 
    62.         } 
    63.     } 
    64.     #endregion 
    65.  
    66.     #region Public Methods 
    67.     /// <summary> 
    68.     /// Always opens a new session from the session factory. 
    69.  /// </summary> 
    70.     /// <returns>The newly opened session.</returns> 
    71.  public ISession OpenSession() 
    72.     { 
    73.         this.session = sessionFactory.OpenSession(); 
    74.         return this.session; 
    75.     } 
    76.     #endregion 
    77.  
    78. } 

    简单小结一下。通过上面的例子可以看到,仓储的实现是不能依赖于任何技术细节的,因为领域模型并不关心技术问题。这并不是DDD一书中怎么说,我们 就得怎么做。事实上的确如此,因为DDD的思想,使得我们应该把关注点放在业务分析与领域建模上,而仓储实现的分离正是这一思想的具体表现形式。不管怎么 样,采用其它的手段也罢,我们还是应该遵循“将关注点放在领域”这一宗旨。

    接下来看如何在领域层结合IoC框架使用仓储。仍然以Castle Windsor为例。配置文件如下(超长部分我用省略号去掉了):

    隐藏行号 复制代码 Castle Windsor配置
    1. <castle> 
    2.   <components> 
    3.     <!-- Object Context for Entity Data Model --> 
    4.     <component id="ObjectContext" 
    5.                service="System.Data.Objects.ObjectContext, System.Data.Entity, Version=4.0.0.0,......"  
    6.                type="EasyCommerce.Domain.Model.EntitiesContainer, EasyCommerce.Domain"/> 
    7.  
    8.     <component id="GeneralRepository" 
    9.                service="EasyCommerce.Domain.IRepository`1[[EasyCommerce.Domain.Model.Customer, ......" 
    10.                type="EasyCommerce.Infrastructure.Repositories.EdmRepositories.EdmRepository`1[[EasyCo......"> 
    11.       <objContext>${ObjectContext}</objContext> 
    12.     </component> 
    13.  
    14.     <component id="TransactionContext" 
    15.                service="EasyCommerce.Domain.IRepositoryTransactionContext, EasyCommerce.Domain......" 
    16.                type="EasyCommerce.Infrastructure.Repositories.EdmRepositories.EdmRepositoryTransactionContext, ..."> 
    17.     </component> 
    18.  
    19.   </components> 
    20. </castle> 
    21.  

    以下是调用代码:

    隐藏行号 复制代码 调用方代码
    1. [TestMethod] 
    2. public void TestCreateCustomer() 
    3. { 
    4.     IWindsorContainer container = new WindsorContainer(new XmlInterpreter()); 
    5.     using (IRepositoryTransactionContext tx = container.GetService<IRepositoryTransactionContext>()) 
    6.     { 
    7.         tx.BeginTransaction(); 
    8.         try 
    9.  { 
    10.             Customer customer = Customer.CreateCustomer("daxnet", "12345", 
    11.                 new Name { FirstName = "Sunny", LastName = "Chen" }, 
    12.                 new Address(), new Address(), DateTime.Now.AddYears(-29)); 

    13.             IRepository<Customer> customerRepository = tx.GetRepository<Customer>(); 
    14.             customerRepository.Add(customer); 

    15.             tx.Commit(); 
    16.         } 
    17.         catch 
    18.  { 
    19.             tx.Rollback(); 
    20.             throw; 
    21.         } 
    22.     } 
    23. } 

    测试结果及数据库数据结果:

    image

    image

    注意】:在使用NHibernate的仓储实现时,由于NHibernate的延迟加载特性,需要将实体的属 性设置为virtual,以便由NHibernate产生Proxy Class进而实现延迟加载;但是由EntityFramework自动生成的源代码并不会将实体属性设置为virtual,而采用partial class也无法解决这个问题。因此需要在代码生成技术上做文章。我的做法是,针对edmx产生一个基于T4的代码生成模板,然后修改这个模板,分别在 WritePrimitiveTypeProperty和WriteComplexTypeProperty方法中的适当位置加上virtual关键字:

    隐藏行号 复制代码 WritePrimitiveTypeProperty
    1.     private void WritePrimitiveTypeProperty(EdmProperty primitiveProperty, CodeGenerationTools code) 
    2.     { 
    3.         MetadataTools ef = new MetadataTools(this); 
    4. #> 

    5.     /// <summary> 
    6.     /// <#=SummaryComment(primitiveProperty)#> 
    7.     /// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#> 
    8.     [EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>, 
    9. IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)] 
    10.     [DataMemberAttribute()] 
    11.     <#=code.SpaceAfter(NewModifier(primitiveProperty))#><#=Accessibility.ForProperty(primitiveProperty)#> virtual
    12.  <#=code.Escape(primitiveProperty.TypeUsage)#> <#=code.Escape(primitiveProperty)#> 
    13.     { 
    14.         <#=code.SpaceAfter(Accessibility.ForGetter(primitiveProperty))#>get 
    15.         { 
    16. <#+             if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[])) 
    17.                 { 
    18. #> 
    19.             return StructuralObject.GetValidValue(<#=code.FieldName(primitiveProperty)#>); 

    隐藏行号 复制代码 WriteComplexTypeProperty
    1.     private void WriteComplexTypeProperty(EdmProperty complexProperty, CodeGenerationTools code) 
    2.     { 
    3. #> 

    4.     /// <summary> 
    5.     /// <#=SummaryComment(complexProperty)#> 
    6.     /// </summary><#=LongDescriptionCommentElement(complexProperty, 1)#> 
    7.     [EdmComplexPropertyAttribute()] 
    8.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
    9.     [XmlElement(IsNullable=true)] 
    10.     [SoapElement(IsNullable=true)] 
    11.     [DataMemberAttribute()] 
    12.     <#=code.SpaceAfter(NewModifier(complexProperty))#><#=Accessibility.ForProperty(complexProperty)#> virtual 
    13. <#=MultiSchemaEscape(complexProperty.TypeUsage, code)#><#=code.Escape(complexProperty)#> 
    14.     { 
    15.         <#=code.SpaceAfter(Accessibility.ForGetter(complexProperty))#>get 
    16.         { 
    17.             <#=code.FieldName(complexProperty)#> = GetValidValue(<#=code.FieldName(complexProperty)#>, 
    18. "<#=complexProperty.Name#>", 
    19. false, <#=InitializedTrackingField(complexProperty, code)#>); 
    20.             <#=InitializedTrackingField(complexProperty, code)#> = true; 

    始终坚持一个原则:不要在生成的代码上直接修改,一是工作量巨大,另一方面就是,代码是自动生成的,今后模型修改了,代码将会重新生成。

  • 相关阅读:
    资源利用率提高67%,腾讯实时风控平台云原生容器化之路
    热门分享预告|腾讯大规模云原生平台稳定性实践
    Fluid + GooseFS 助力云原生数据编排与加速快速落地
    基于 Clusternet 与 OCM 打造新一代开放的多集群管理平台
    案例 | 沃尔玛 x 腾讯云 Serverless 应用实践,全力保障消费者购物体验
    SuperEdge 高可用云边隧道有哪些特点?
    kubernetes 降本增效标准指南|ProphetPilot:容器智能成本管理引擎
    公有云上构建云原生 AI 平台的探索与实践
    如何削减 50% 机器预算?“人机对抗”探索云端之路
    SuperEdge 易学易用系列-SuperEdge 简介
  • 原文地址:https://www.cnblogs.com/quietwalk/p/2229945.html
Copyright © 2011-2022 走看看