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

2 Comments

  • Hi,

    I liked that solution. It enables the service to use the repositories without having to manage the ObjectContext's lifetime. The only thing I would change is the ObjectContext name in your base DomainService and other interfaces. That is too attached to the entity framework. I would prefer IDbContext or something like that.

    I'll be posting in my blog a similar solution to use the EF and the Repository pattern in WCF without the dependency of the ObjectContext within the WCF service. I also thought of leveraging the IExtension mechanism (as SharpArchitecture does to enable the Repositories access the ISession object).

    I'll be posting in a few weeks, because I haven't got the time now :(

    Keep up the good work on Aspen :)


  • @Manuel Felcio:
    At the moment just in global.asax, this will probably be changed, still the code is on a early stage and there will be some changes, at the moment we have to wait for the SL4 RC release until we can some more work..
    About your ObjectContext, that is one way of doing it.. I prefer the way where no Attribute is needed.

Comments have been disabled for this content.