Aspen – part 6 of X – Reusing ObjectContex among Repositories during a DomainSerivce operation.
(I had to shorten the title down a bit ;))
I promised to write about how I have implemented the solution mentioned in my previous post and I’m going to keep my promise. But I will not use this solution in the Aspen project, more about that in the end of the post (under the topic “What will I use in the Aspen Project”).
I will first start with the following code (From my
DomainService):
[Invoke] public void Save() { using (var trans = new TransactionScope()) { _memberRepository.Add(new Member() { FirstName = "Ove", LastName = "Bengt" }); _gatheringRepository.Add(new Member() { FirstName = "OveG", LastName = "BengtG" }); trans.Complete(); } }
By default when we call the TransactionScope Complete method, the Entity Framework ObjectContext inside of the transaction scope will not save any changes. If we use nHibernate within a Transaction scope, it will Flush when we make a call to the Complete method. So what I did was to try to implement a way to let the Complete method call the ObjectContext’s SaveChanges method. If we want to get notified when the Complete method of a transaction is called we can implement the IEnlistmentNofication interface. The interface has several methods, Prepare, Rollback, InDoubt and Commit. Within the Commit we can for example call the ObjectContext’s SaveChanges method:
public class ObjectContextEnlistment : IEnlistmentNotification { private readonly IObjectContext _objectContext; private PreparingEnlistment _preparingEnlistment; public ObjectContextEnlistment(IObjectContext ojbectContext) { if (ojbectContext == null) throw new ArgumentNullException("ojbectContext"); _objectContext = ojbectContext; } public void Prepare(PreparingEnlistment preparingEnlistment) { _preparingEnlistment = preparingEnlistment; _preparingEnlistment.Prepared(); } public void Commit(Enlistment enlistment) { _objectContext.SaveChanges(); enlistment.Done(); } public void Rollback(Enlistment enlistment) { enlistment.Done(); } public void InDoubt(Enlistment enlistment) { enlistment.Done(); } }
Note: The above code may not be perfect and solve all kind of EF scenarios etc, I only used it for testing purpose.
We have to register this IEnlistmentNotification to the
transaction so we can be notified. This is done by using the
Current running transactions EnlistVolatile method:
if (Transaction.Current != null) Transaction.Current.EnlistVolatile( new ObjectContextEnlistment(_objectContext), EnlistmentOptions.None);
I will not use this solution in the Aspen project, It was just a fun thing to do.
In my previous post I also mentioned that I could now reuse an ObjectContext among several Repositories in one Domain Service Operation to reuse the Unity of Work and Identity Map etc, and also automatically dispose the ObjectContext after the operation is completed. lets see how I implemented the solution. Because RIA Services uses WCF, we can get access to the WCF OperationContext. The OperationContext will live during an operation add we can add objects to it. I decided to add the ObjectContext to the OperationContext so every Repository inside a WCF operation will get access to the same ObjectContext from the OperationContext. In Part 3 I wrote about the Repository implementation and also about my IObjectContext interface. Repositories will take an IObjectContext when it’s created. The Repository will then use this interface to get access to an ObjectContext.
I made some changes since the Part 3 implementation. Now I
have a generic BaseRepository<TEntity>. Here is the
code for the BaseRepository<TEntity>:
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class { readonly IObjectContext _objectContext = null; readonly IObjectSet<TEntity> _objectSet = null; public BaseRepository(IObjectContext objectContext) { if (objectContext == null) throw new ArgumentNullException("objectContext"); _objectContext = objectContext; _objectSet = _objectContext.CreateObjectSet<TEntity>(); } public IQueryable<TEntity> GetQuery() { return _objectSet; } public IEnumerable<TEntity> GetAll() { return _objectSet.ToList(); } public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> where) { Contract.Ensures(Contract.Result<IEnumerable<TEntity>>() != null); return _objectSet.Where(where); } public TEntity Single(Expression<Func<TEntity, bool>> where) { return _objectSet.SingleOrDefault(where); } public void Delete(TEntity entity) { _objectSet.DeleteObject(entity); } public void Add(TEntity entity) { _objectSet.AddObject(entity); } }
Every repository will inherits from this base class. As you
can see the constructor will take an IObjectContext. I
created an IObjectContext and let it add an ObjectContext to
the WCF Operation’s OperationContext. So when the
Repositories inside of an operation request an
ObjectContext, it will get the one added to the
OperationContext. By doing so, all Repositories used inside
of an operation will use the same ObjectContext. Here is the
implementation of the IObjectContext:
public class WcfObjectContextAdapter : IObjectContext { public IObjectSet<T> CreateObjectSet<T>() where T : class { return Context.CreateObjectSet<T>(); } public void SaveChanges() { Context.SaveChanges(); } public void Dispose() { Context.Dispose(); } public WcfObjectContextAdapter(IObjectContextFactory objectContextFactory) { if (objectContextFactory == null) throw new ArgumentNullException("sessionFactory"); if (Context == null) Context = objectContextFactory.Create(); } public ObjectContext Context { get { if (WcfOperationContainerExtension.Current == null) return null; return WcfOperationContainerExtension.Current.Context; } set { if (CurrentOperationContext == null) return; CurrentOperationContext.Extensions.Add(new WcfOperationContainerExtension(value)); CurrentOperationContext.OperationCompleted += CurrentOperationContext_OperationCompleted; } } private void CurrentOperationContext_OperationCompleted(object sender, EventArgs e) { var context = WcfOperationContainerExtension.Current.Context; context.Dispose(); } private OperationContext CurrentOperationContext { get { return OperationContext.Current; } } private class WcfOperationContainerExtension : IExtension<OperationContext> { public ObjectContext Context { get; set; } public WcfOperationContainerExtension(ObjectContext context) { Context = context; } public void Attach(OperationContext owner) { } public void Detach(OperationContext owner) { } public static WcfOperationContainerExtension Current { get { if (OperationContext.Current == null) return null; return OperationContext.Current.Extensions.Find<WcfOperationContainerExtension>(); } } } }
The WcfOperationContextAdapter will use an WCF
IExtension<T> to keep the ObjectContext in the
OperationContext, and also Dispose the ObjectContext when
the OperationCompleted event is raised.
TheWcfOperationContextAdapter takes as an argument of type
IObjetContextFactory, it’s the factory which will configure
and create an ObjectContext. In
Part 2
I wrote about a ObjectContext factory, I made some changes
to it and here is the code for new one:
public class DefaultObjectContextFactory : IObjectContextFactory { [ImportMany(typeof(StructuralTypeConfiguration), AllowRecomposition = true)] private List<StructuralTypeConfiguration> _configurations = null; public ObjectContext Create() { return this.Create(ConfigurationManager.ConnectionStrings["AspenConnectionString"].ConnectionString); } public ObjectContext Create(string connectionString) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentNullException("connectionString"); return this.Create(new SqlConnection(connectionString)); } public ObjectContext Create(DbConnection con) { if (con == null) throw new ArgumentNullException("con"); if (_configurations == null) this.Compose(); var builder = CreateContextBuilder(); return builder.Create(con); } private ContextBuilder<ObjectContext> CreateContextBuilder() { var builder = new ContextBuilder<ObjectContext>(); if (_configurations != null) _configurations.ForEach(c => builder.Configurations.Add(c)); return builder; } private void Compose() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); using (var catalogParts = new AggregateCatalog()) { foreach (var assembly in assemblies) { if (assembly.FullName.Contains("EntityMapping")) catalogParts.Catalogs.Add( new AssemblyCatalog(assembly)); } using (var container = new CompositionContainer(catalogParts)) { container.ComposeParts(this); } } } }
How will all this thing works together you may ask? Well
here is the process:
When a call to a
DomainService is made, the custom DomainService Factory (The
code for the custom DomainService factory can be found in
Part 4) will use Unity to resolve the DomainService. Unity will
create the WcfOperationContextAdapter and pass an instance
of the DefaultObjectContextFactory to the
WcfOperationContextAdapter’s constructor. The
WcfOperationContextAdapter will then be injected to the
MemberRepostiory’s constructor. The MemberRepository will be
passed into the DomainService. Here is the Unity container
configuration:
Container.RegisterType<MemberDomainService>();
Container.RegisterType<IMemberRepository, MemberRepository>();
Container.RegisterType<IGatheringRepository, GatheringRepository>();
Container.RegisterType<IObjectContext, WcfObjectContextAdapter>(
new InjectionConstructor(new DefaultObjectContextFactory()));
Here is the an example of a DomainService:
[EnableClientAccess()] public class MemberDomainService : DomainService { readonly IMemberRepository _memberRepository = null; readonly IGatheringRepository _gatheringRepository = null; public MemberDomainService(IMemberRepository membershipRepository, IGatheringRepository gatheringRepository) { if (membershipRepository == null) throw new ArgumentNullException("membershipRepository"); _memberRepository = membershipRepository; _gatheringRepository = gatheringRepository; } [Invoke] public void Save() { using (var trans = new TransactionScope()) { _memberRepository.Add(new Member() { FirstName = "Ove", LastName = "Bengt" }); _gatheringRepository.Add(new Member() { FirstName = "OveG", LastName = "BengtG" }); trans.Complete(); } } public IEnumerable<MemberDto> GetMembers() { var test = _gatheringRepository.GetAll(); var members = _memberRepository.GetAll(); return members.Select(m => Mapper.Map<Member, MemberDto>(m)); } }
Note: This DomainService will not be used in the Aspen
project, it only do some stupid irrelevant stuffs, I
only use it for testing purpose.
What will I use in the Aspen Project
I will keep the ObjectContext in an OperationContext,
but will instead handle the Dispose and SaveChanges in a
custom DomainService. Here is the temporary code I use and
still on a testing stage:
public class ObjectContextDomainService : DomainService { private IObjectContext _objectContext; public void Initialize( IObjectContext objectContext, DomainServiceContext domainServiceContext) { _objectContext = objectContext; base.Initialize(domainServiceContext); } protected override bool PersistChangeSet(ChangeSet changeSet) { _objectContext.SaveChanges(); return base.PersistChangeSet(changeSet); } public override object Invoke( DomainOperationEntry operation, object[] parameters, out IEnumerable<ValidationResult> validationErrors) { var result = base.Invoke(operation, parameters, out validationErrors); _objectContext.SaveChanges(); return result; } protected override void Dispose(bool disposing) { _objectContext.Dispose(); base.Dispose(disposing); } }
Here is how the earlier MemberDomainService will now look
like:
[EnableClientAccess()] public class MemberDomainService : ObjectContextDomainService { readonly IMemberRepository _memberRepository = null; readonly IGatheringRepository _gatheringRepository = null; public MemberDomainService(IMemberRepository membershipRepository,
IGatheringRepository gatheringRepository) { if (membershipRepository == null) throw new ArgumentNullException("membershipRepository"); _memberRepository = membershipRepository; _gatheringRepository = gatheringRepository; } [Invoke] public void Save() { _memberRepository.Add(new Member() { FirstName = "Ove34", LastName = "Bengt" }); _gatheringRepository.Add(new Member() { FirstName = "Ove34G", LastName = "BengtG" }); } public IEnumerable<MemberDto> GetMembers() { var test = _gatheringRepository.GetAll(); var members = _memberRepository.GetAll(); return members.Select(m => Mapper.Map<Member, MemberDto>(m)); } }
When the Save Invoke operation will be called the Member-
and GatheringRepository will still use the same
ObjectContext and when the Operation is done the SaveChanges
of the ObjectContext will be called and also the Dispose
method. For Add, Insert and Delete operations the
ObjectContext will be called when the DomainService
PersistChangeSet method is called and also do the Dispose of
the ObjectContext. By using this solution, we can now share
the UoW among Repositories and we don’t need to make a call
to the SubmitChanges and we don’t need to Dispose it. The
SubmitChanges of the Entity Framework ObjectContext will
also use an implicit transaction, so we don’t need to use
the TransactionScope class. This solution will now remove
some cross-cutting concerns.
The code for the Aspen project is on CodePlex, but the project is not yet published, we will in a near future publish the project.
If you want to know when I will publish a new blog
post, you can follow me on twitter:
http://www.twitter.com/fredrikn