Testing UnitOfWork with EntityFramework 7


, ,

EF 7 is a lighter weight  version of EF to date and can be easily extended.  It has been re-engineered by the EF team at MSFT to be forward looking and address today’s pertinent scenarios including but not limited to non-relational data store, multiple platforms and today’s topic: test scenarios with its InMemory data store.

Beside supporting SqlServer and SQLite, EF7 provide out of box suppport for in-memory data store as an optional backing data store for your entities.  To get started using the in-memory store, install the EntityFramework.InMemory package and then use the extension method for DbContextOptions or DbContextOptionsBuilder or use an instance of InMemoryOptionsExtension, which derives from IDbContextOptionsExtension.  However the InMemoryOptionsExtension does not seem to achieve anything so the first two options would be the recommended ones.


UnitOfWork brief intro

To keep it short, Unit-of-Work pattern allows my Repositories to share a single backing store context and not run into an inevitable out of sync Repositories each having different entities versions.  Its magic and usefulness really show itself in modularized apps.

Here are the essentials of my DAL

public class UnitOfWorkBase : IDisposable
        private bool _disposed = false;
        protected DbContext DbContext { get; set; }

        public void Save()
            if (_disposed)
                throw new ObjectDisposedException("DbContext", "context has been disposed");

            if (DbContext == null)
                throw new NullReferenceException("DbContext is null.  DbContext has not been instantiated");

        public StateManager StateManager
            get { return ((IAccessor<IServiceProvider>)DbContext).Service.GetRequiredService<IStateManager>() as StateManager; }

        public void Dispose()

        protected virtual void Dispose(bool disposing)
                    if(DbContext != null)
                        DbContext = null;
            _disposed = true;


and the Repository

public class Repository<T, K> : IRepository<T,K> where T: class, IEntity<K> 

        internal DbContext DbContext { get; set; }
        internal DbSet<T> Entities { get; set; }

        /// <summary>
        /// pass in an initialized <see cref="DbContext"/> for T
        /// </summary>
        /// <param name="context">DbContext used to communicate with the backend</param>
        public Repository(DbContext context)
            this.DbContext = context;

            ///this.Entities = ((IAccessor<IServiceProvider>)DbContext).Service.GetRequiredService<IDbSetInitializer>().CreateSet<T>(DbContext);
            this.Entities = this.DbContext.Set<T>();

        public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null,
            Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
           IEnumerable<KeyValuePair<Type, String>> includedProperties = null
            IQueryable<T> query = Entities;


            return query.ToList();

        public virtual T FindById(K identifier)
            return Entities.FirstOrDefault(t => t.key.Equals(identifier));

        public virtual void Add(T entity)

        public virtual void Update(T entity)

        public virtual void Delete (K identifier)

        public virtual void Delete(T entity)


*/ // back to the topic

The usual scenario with EntitiFramwork is to create a custom context class that derives from DbContext  and override OnConfiguring(DbContextOptionsBuilder optionsBuilder)  and also (depending on customization and control required ) OnModelCreating(ModelBuilder modelBuilder).  Overriding onConfiguration  allow us  to configure the context using extension methods on the DbContextOptionBuilder  parameter passed into the method; such as whether to use Sqlite or InMemory store. but in test scenario perhaps every time you test your repositories, you do not want to do that repetitive work of creating a class derived from DbContext , and prefer building the  options outside of the context  and reusing the base DbContext .

After using nuget package manager to install xUnit.net and xUnit.net[Runner: Visual Studio], I defined a InMemoryUnitOfWork class that derives from my UnitOfWorkBase since it is exactly that I am test “ how and Is my specific UnitOfWork implementation behaving correctly?” and two entities to help out with the test.  Listed below in order are the entites, the InMemoryUnitOfWork, and the Test case.

public class Parent : IEntity<String>
        private string _id;
        public string Name { get; set; }
        public DateTime BirthDate { get; set; }

       public String ParentId { get {
            return _id;
            set { _id = value; }
        String IEntity<string>.key
        { get {     return _id;  }  set { }  
        public virtual ICollection<Enfant> Children { get; set; }
public class Enfant: IEntity<String>
        public String EnfantId { get; set; }
        [NotMapped] String IEntity<string>.key
        {  get {  return EnfantId; } set {} }

        public Parent Parent { get; set; }
        public DateTime BirthDate { get; set; }
        public String Prenom { get; set; }


The Specific InMemoryUnitOfWork

public class InMemoryUnitOfWork : UnitOfWorkBase // check out the comments: they can show alternative routes and actions useful during debugging
        public InMemoryUnitOfWork()
            var convention = new Microsoft.Data.Entity.Metadata.Conventions.ConventionSet();

            ///Additional information: The instance of entity type 'WoroArch.RepositoryTest.Parent' cannot be tracked because it has an invalid (e.g. null or CLR default) primary key. Either set the key explicitly or consider using an IValueGenerator to generate unique key values
            var keyConvention = new KeyConvention();

            var model = new Microsoft.Data.Entity.ModelBuilder(convention); // required for interaction with DbSet<>
            model.Entity<Parent> ((entTypeBldr) => {
               var keyBldr =  entTypeBldr.Key(p => p.ParentId);
                //keyBldr.Metadata.AddAnnotation("PrimKey", autoGenConvention); //what is the purpose of Annotation in this context?
                //var internalKeyBldr = ((IAccessor<InternalKeyBuilder>)keyBldr).Service;
                //var metadatProperty = internalKeyBldr.Metadata.Properties[0]; /** to be removed to see if Error is still generated*/
                //metadatProperty.IsNullable = false;
                //metadatProperty.IsReadOnlyBeforeSave = false;
                //metadatProperty.IsReadOnlyAfterSave = true;
                //metadatProperty.RequiresValueGenerator = true;
                //metadatProperty.ValueGenerated = ValueGenerated.OnAdd;

               // entTypeBldr.Property<String>(p => p.ParentId).ValueGeneratedNever();
                entTypeBldr.Collection<Enfant>(p => p.Children);
                entTypeBldr.Property<string>(p => p.Name);
                entTypeBldr.Property<DateTime>(p => p.BirthDate);

               // keyConvention.Apply(internalKeyBldr);  // is this possible?

           // model.Entity<Parent>().Property(p => p.Id).ValueGeneratedNever();
            //model.Entity<Parent>().Property(p => p.Id).Metadata.SentinelValue = "Sentinel Val";

            model.Entity<Enfant>(entTypeBldr => {
                entTypeBldr.Key(enf => enf.EnfantId);
                entTypeBldr.Property<string>(enf => enf.Prenom);
                entTypeBldr.Property<DateTime>(enf => enf.BirthDate);
                entTypeBldr.Reference<Parent>(enf => enf.Parent);            

            //var genCache = model.GetService<IValueGeneratorCache>();
            //var valueGenSelector = model.GetService<IValueGeneratorSelector>(); // is this usefull for generating primary keys?

            var option = new DbContextOptions<DbContext>();

            //var inMemOptionsEx = new InMemoryOptionsExtension();

            var contextBuilder = new DbContextOptionsBuilder<DbContext>(option);

            DbContext = new DbContext(contextBuilder.Options);            

            ParentRepository = new Repository<Parent, string>(DbContext);
            EnfantsRepository = new Repository<Enfant, string>(DbContext);

            //Init the sets

            //how is my stateManager looking?? hmm:)
           // var stateManager = ServiceProviderCache.Instance.GetOrAdd(contextBuilder.Options).GetRequiredService<IStateManager>() as StateManager;           

        public Repository<Parent,String> ParentRepository { get; private set; }

        public Repository<Enfant, String> EnfantsRepository { get; private set; }

        public DbSet<Parent> Parents { get { return ParentRepository.Entities; } }

        public DbSet<Enfant> Enfants { get { return EnfantsRepository.Entities; } }

aNd now the Test case

public class UnitOfWorkTest
        private InMemoryUnitOfWork _unOfWrk; 

        public UnitOfWorkTest()
            _unOfWrk = new InMemoryUnitOfWork();

        public void UnitOfWorkConstruct_RepositoriesAreInstantiated()

        public void ContextSaves_whenAddingEntities_ToRepositories()
            var parent = _unOfWrk.Parents.Add(new Parent() { BirthDate = new DateTime(1865, 05, 21), Name = "Princess Dougo", ParentId = (Guid.NewGuid()).ToString() }); 

            _unOfWrk.EnfantsRepository.Add(new Enfant() { Parent = parent.Entity, BirthDate = new DateTime(1880, 10, 11), Prenom = "Niankoro", EnfantId = (Guid.NewGuid()).ToString() });
            _unOfWrk.EnfantsRepository.Add(new Enfant() { Parent = parent.Entity, BirthDate = new DateTime(1880, 10, 11), Prenom = "Boundialie", EnfantId = (Guid.NewGuid()).ToString() }); 


            _unOfWrk.EnfantsRepository.Get(null, null, new KeyValuePair<Type, String>[]{ new KeyValuePair<Type, String>(typeof(Parent), "Parent") }); 

            Assert.Same(_unOfWrk.Enfants.First().Parent, _unOfWrk.Parents.First());
            Assert.Collection(_unOfWrk.Enfants, (enf) => { String.IsNullOrEmpty(enf.Prenom); }, (enf) => { });

the only problem I am currently encountering with this setup is the following exception when I do not manually set values for the Ids:

Test Name:    WoroArch.RepositoryTest.UnitOfWorkTest.ContextSaves_whenAddingEntities_ToRepositories
Test FullName:    WoroArch.RepositoryTest.UnitOfWorkTest.ContextSaves_whenAddingEntities_ToRepositories
Test Source:    ***\WoroArch.RepositoryTest\UnitOfWorkTest.cs : line 32
Test Outcome:    Failed
Test Duration:    0:00:00.949

Result Message:    System.InvalidOperationException : The property ‘ParentId’ on entity type ‘Parent’ has a temporary value while attempting to change the entity’s state to ‘Unchanged’. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
Result StackTrace:
at Microsoft.Data.Entity.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
at Microsoft.Data.Entity.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges)
at Microsoft.Data.Entity.ChangeTracking.Internal.InternalEntityEntry.AcceptChanges()
at Microsoft.Data.Entity.ChangeTracking.Internal.StateManager.AcceptAllChanges(IReadOnlyList`1 changedEntries)
at Microsoft.Data.Entity.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.Data.Entity.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.Data.Entity.DbContext.SaveChanges()
at WoroArch.Repository.UnitOfWorkBase.Save() in ***\WoroArch.Repository\UnitOfWorkBase.cs:line 30
at WoroArch.RepositoryTest.UnitOfWorkTest.ContextSaves_whenAddingEntities_ToRepositories() in ***\WoroArch.RepositoryTest\UnitOfWorkTest.cs:line 42


this issue seem to be arising only with InMemory Store


Get every new post delivered to your Inbox.