早在年前的時候就已經在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
-
public interface IRepositoryTransactionContext : IDisposable
-
{
-
IRepository<TEntity> GetRepository<TEntity>()
-
where TEntity : EntityObject, IAggregateRoot;
-
void BeginTransaction();
-
void Commit();
-
void Rollback();
-
}
-
上面第三行代碼定義了一個接口方法,這個方法的主要作用就是返回一個針對指定聚合根實體的倉儲實例。剩下那三行代碼就很明顯了,那是標準的transaction操作:啓動事務、提交事務以及回滾事務。
在設計上,可以根據需要,選擇合適的技術來實現IRepositoryTransactionContext。我們現在討論的是EntityFramework,所以我將給出EntityFramework的具體實現。當然,如果你不選用EntityFramework,而是用NHibernate實現數據持久化,這樣的設計同樣能夠使你達到目的。以下是基於EntityFramework的實現:EdmRepositoryTransactionContext的僞代碼。
隱藏行號 複製代碼 ?EdmRepositoryTransactionContext
-
internal class EdmRepositoryTransactionContext : IRepositoryTransactionContext
-
{
-
private ObjectContext objContext;
-
private Dictionary<Type, object> repositoryCache = new Dictionary<Type, object>();
-
-
public EdmRepositoryTransactionContext(ObjectContext objContext)
-
{
-
this.objContext = objContext;
-
}
-
-
#region IRepositoryTransactionContext Members
-
-
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : EntityObject, IAggregateRoot
-
{
-
if (repositoryCache.ContainsKey(typeof(TEntity)))
-
{
-
return (IRepository<TEntity>)repositoryCache[typeof(TEntity)];
-
}
-
IRepository<TEntity> repository = new EdmRepository<TEntity>(this.objContext);
-
this.repositoryCache.Add(typeof(TEntity), repository);
-
return repository;
-
}
-
-
public void BeginTransaction()
-
{
-
// We do not need to begin a transaction here because the object context,
-
// which would handle the transaction, was created and injected into the
-
// constructor by Castle Windsor framework.
-
}
-
-
public void Commit()
-
{
-
this.objContext.SaveChanges();
-
}
-
-
public void Rollback()
-
{
-
// We also do not need to perform the rollback operation because
-
// entity framework will handle this for us, just when the execution
-
// point is stepping out of the using scope.
-
}
-
-
#endregion
-
-
#region IDisposable Members
-
-
public void Dispose()
-
{
-
this.repositoryCache.Clear();
-
this.objContext.Dispose();
-
}
-
-
#endregion
-
}
-
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實現
-
internal class NHibernateRepositoryTransactionContext : IRepositoryTransactionContext
-
{
-
ITransaction transaction;
-
Dictionary<Type, object> repositoryCache = new Dictionary<Type, object>();
-
-
public ISession Session { get { return DatabaseSessionFactory.Instance.Session; } }
-
-
#region IRepositoryTransactionContext Members
-
-
public IRepository<TEntity> GetRepository<TEntity>()
-
where TEntity : EntityObject, IAggregateRoot
-
{
-
if (repositoryCache.ContainsKey(typeof(TEntity)))
-
{
-
return (IRepository<TEntity>)repositoryCache[typeof(TEntity)];
-
}
-
IRepository<TEntity> repository = new NHibernateRepository<TEntity>(this.Session);
-
this.repositoryCache.Add(typeof(TEntity), repository);
-
return repository;
-
}
-
-
public void BeginTransaction()
-
{
-
transaction = DatabaseSessionFactory.Instance.Session.BeginTransaction();
-
}
-
-
public void Commit()
-
{
-
transaction.Commit();
-
}
-
-
public void Rollback()
-
{
-
transaction.Rollback();
-
}
-
-
#endregion
-
-
#region IDisposable Members
-
-
public void Dispose()
-
{
-
transaction.Dispose();
-
ISession dbSession = DatabaseSessionFactory.Instance.Session;
-
if (dbSession != null && dbSession.IsOpen)
-
dbSession.Close();
-
}
-
-
#endregion
-
}
-
隱藏行號 複製代碼 ?NHibernateRepository實現
-
internal class NHibernateRepository<TEntity> : IRepository<TEntity>
-
where TEntity : EntityObject, IAggregateRoot
-
{
-
ISession session;
-
-
public NHibernateRepository(ISession session)
-
{
-
this.session = session;
-
}
-
-
#region IRepository<TEntity> Members
-
-
public void Add(TEntity entity)
-
{
-
this.session.Save(entity);
-
}
-
-
public TEntity GetByKey(int id)
-
{
-
return (TEntity)this.session.Get(typeof(TEntity), id);
-
}
-
-
public IEnumerable<TEntity> FindBySpecification(Func<TEntity, bool> spec)
-
{
-
throw new NotImplementedException();
-
}
-
-
public void Remove(TEntity entity)
-
{
-
this.session.Delete(entity);
-
}
-
-
public void Update(TEntity entity)
-
{
-
this.session.SaveOrUpdate(entity);
-
}
-
-
#endregion
-
}
-
在NHibernateRepositoryTransactionContext中使用了一個DatabaseSessionFactory的類,該類主要用於管理NHibernate的Session對象,具體實現如下(該實現已被用於我的Apworks應用開發框架原型中):
隱藏行號 複製代碼 ?DatabaseSessionFactory實現
-
/// <summary>
-
/// Represents the factory singleton for database session.
-
/// </summary>
-
internal sealed class DatabaseSessionFactory
-
{
-
#region Private Static Fields
-
/// <summary>
-
/// The singleton instance of the database session factory.
-
/// </summary>
-
private static readonly DatabaseSessionFactory databaseSessionFactory = new DatabaseSessionFactory();
-
#endregion
-
-
#region Private Fields
-
/// <summary>
-
/// The session factory instance.
-
/// </summary>
-
private ISessionFactory sessionFactory = null;
-
/// <summary>
-
/// The session instance.
-
/// </summary>
-
private ISession session = null;
-
#endregion
-
-
#region Constructors
-
/// <summary>
-
/// Privately constructs the database session factory instance, configures the
-
/// NHibernate framework by using the assemblies listed in the configured spaces(paths)
-
/// that are decorated by <see cref="EntityVisibleAttribute"/>.
-
/// </summary>
-
private DatabaseSessionFactory()
-
{
-
Configuration nhibernateConfig = new Configuration();
-
nhibernateConfig.Configure();
-
nhibernateConfig.AddAssembly(typeof(IAggregateRoot).Assembly);
-
sessionFactory = nhibernateConfig.BuildSessionFactory();
-
}
-
#endregion
-
-
#region Public Properties
-
/// <summary>
-
/// Gets the singleton instance of the database session factory.
-
/// </summary>
-
public static DatabaseSessionFactory Instance
-
{
-
get
-
{
-
return databaseSessionFactory;
-
}
-
}
-
-
/// <summary>
-
/// Gets the singleton instance of the session. If the session has not been
-
/// initialized or opened, it will return a newly opened session from the session factory.
-
/// </summary>
-
public ISession Session
-
{
-
get
-
{
-
ISession result = session;
-
if (result != null && result.IsOpen)
-
return result;
-
return OpenSession();
-
}
-
}
-
#endregion
-
-
#region Public Methods
-
/// <summary>
-
/// Always opens a new session from the session factory.
-
/// </summary>
-
/// <returns>The newly opened session.</returns>
-
public ISession OpenSession()
-
{
-
this.session = sessionFactory.OpenSession();
-
return this.session;
-
}
-
#endregion
-
-
}
-
簡單小結一下。通過上面的例子可以看到,倉儲的實現是不能依賴於任何技術細節的,因爲領域模型並不關心技術問題。這並不是DDD一書中怎麼說,我們就得怎麼做。事實上的確如此,因爲DDD的思想,使得我們應該把關注點放在業務分析與領域建模上,而倉儲實現的分離正是這一思想的具體表現形式。不管怎麼樣,採用其它的手段也罷,我們還是應該遵循“將關注點放在領域”這一宗旨。
接下來看如何在領域層結合IoC框架使用倉儲。仍然以Castle Windsor爲例。配置文件如下(超長部分我用省略號去掉了):
隱藏行號 複製代碼 ?Castle
Windsor配置
-
<castle>
-
<components>
-
<!-- Object Context for Entity Data Model -->
-
<component id="ObjectContext"
-
service="System.Data.Objects.ObjectContext, System.Data.Entity, Version=4.0.0.0,......"
-
type="EasyCommerce.Domain.Model.EntitiesContainer, EasyCommerce.Domain"/>
-
-
<component id="GeneralRepository"
-
service="EasyCommerce.Domain.IRepository`1[[EasyCommerce.Domain.Model.Customer, ......"
-
type="EasyCommerce.Infrastructure.Repositories.EdmRepositories.EdmRepository`1[[EasyCo......">
-
<objContext>${ObjectContext}</objContext>
-
</component>
-
-
<component id="TransactionContext"
-
service="EasyCommerce.Domain.IRepositoryTransactionContext, EasyCommerce.Domain......"
-
type="EasyCommerce.Infrastructure.Repositories.EdmRepositories.EdmRepositoryTransactionContext, ...">
-
</component>
-
-
</components>
-
</castle>
-
以下是調用代碼:
隱藏行號 複製代碼 ?調用方代碼
-
[TestMethod]
-
public void TestCreateCustomer()
-
{
-
IWindsorContainer container = new WindsorContainer(new XmlInterpreter());
-
using (IRepositoryTransactionContext tx = container.GetService<IRepositoryTransactionContext>())
-
{
-
tx.BeginTransaction();
-
try
-
{
-
Customer customer = Customer.CreateCustomer("daxnet", "12345",
-
new Name { FirstName = "Sunny", LastName = "Chen" },
-
new Address(), new Address(), DateTime.Now.AddYears(-29));
-
-
IRepository<Customer> customerRepository = tx.GetRepository<Customer>();
-
customerRepository.Add(customer);
-
-
tx.Commit();
-
}
-
catch
-
{
-
tx.Rollback();
-
throw;
-
}
-
}
-
}
-
測試結果及數據庫數據結果:
【注意】:在使用NHibernate的倉儲實現時,由於NHibernate的延遲加載特性,需要將實體的屬性設置爲virtual,以便由NHibernate產生Proxy Class進而實現延遲加載;但是由EntityFramework自動生成的源代碼並不會將實體屬性設置爲virtual,而採用partial class也無法解決這個問題。因此需要在代碼生成技術上做文章。我的做法是,針對edmx產生一個基於T4的代碼生成模板,然後修改這個模板,分別在WritePrimitiveTypeProperty和WriteComplexTypeProperty方法中的適當位置加上virtual關鍵字:
隱藏行號 複製代碼 ?WritePrimitiveTypeProperty
-
private void WritePrimitiveTypeProperty(EdmProperty primitiveProperty, CodeGenerationTools code)
-
{
-
MetadataTools ef = new MetadataTools(this);
-
#>
-
-
/// <summary>
-
/// <#=SummaryComment(primitiveProperty)#>
-
/// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#>
-
[EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>,
-
IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)]
-
[DataMemberAttribute()]
-
<#=code.SpaceAfter(NewModifier(primitiveProperty))#><#=Accessibility.ForProperty(primitiveProperty)#> virtual
-
<#=code.Escape(primitiveProperty.TypeUsage)#> <#=code.Escape(primitiveProperty)#>
-
{
-
<#=code.SpaceAfter(Accessibility.ForGetter(primitiveProperty))#>get
-
{
-
<#+ if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[]))
-
{
-
#>
-
return StructuralObject.GetValidValue(<#=code.FieldName(primitiveProperty)#>);
-
隱藏行號 複製代碼 ?WriteComplexTypeProperty
-
private void WriteComplexTypeProperty(EdmProperty complexProperty, CodeGenerationTools code)
-
{
-
#>
-
-
/// <summary>
-
/// <#=SummaryComment(complexProperty)#>
-
/// </summary><#=LongDescriptionCommentElement(complexProperty, 1)#>
-
[EdmComplexPropertyAttribute()]
-
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
-
[XmlElement(IsNullable=true)]
-
[SoapElement(IsNullable=true)]
-
[DataMemberAttribute()]
-
<#=code.SpaceAfter(NewModifier(complexProperty))#><#=Accessibility.ForProperty(complexProperty)#> virtual
-
<#=MultiSchemaEscape(complexProperty.TypeUsage, code)#><#=code.Escape(complexProperty)#>
-
{
-
<#=code.SpaceAfter(Accessibility.ForGetter(complexProperty))#>get
-
{
-
<#=code.FieldName(complexProperty)#> = GetValidValue(<#=code.FieldName(complexProperty)#>,
-
"<#=complexProperty.Name#>",
-
false, <#=InitializedTrackingField(complexProperty, code)#>);
-
<#=InitializedTrackingField(complexProperty, code)#> = true;
-
始終堅持一個原則:不要在生成的代碼上直接修改,一是工作量巨大,另一方面就是,代碼是自動生成的,今後模型修改了,代碼將會重新生成。
出自:http://www.cnblogs.com/daxnet/archive/2010/07/10/1774706.html