Generic Repository: Fake IDbSet implementation update (Find Method & Identity key)

UPDATE (again) Just a quick one: see https://github.com/refactorthis/GraphDiff/blob/master/EFDetachedUpdate/DetachedUpdate/DbContextExtensions.cs on line 209 for a replacement GetKeyProperties method which allows for convention and fluent API mapped keys (You no longer need to annotate your model with KeyAttribute)

UPDATE: Thanks to Eli Weinstock-Herman for pointing out the fact that Find should return null if no result is found (SingleOrDefault instead of Single). Cheers Eli.

Hey guys,

I’ve been back in the coding seat lately creating a new generic repository for a system that we are building. I’ve made some improvements to the FakeDbSet that I posted about earlier Here.

I want to add some notes to the previous post which are long enough they warrant a new post. Firstly, IT IS MUCH EASIER if you do not use foreign keys in your objects but instead use ‘association’ object references. This means you will not have to co-ordinate two different fields when setting up test data. Of course EF does this for you when connected to the database but in memory you would have to do this yourself.

Secondly the implementation of find was quite hard, though I believe I have come up with an elegant generic solution. If you look at the IDbSet documentation MSDN you will see that Find() expects the keys to passed in “the same order that they are defined in the model”.

If I use reflection to find my key properties I can then iterate through the keys and ensure that each object given in the find method equals the value of that key, as shown below.


        private List<PropertyInfo> _keyProperties;

        public virtual T Find(params object[] keyValues)
        {
            if (keyValues.Length != _keyProperties.Count)
                throw new ArgumentException("Incorrect number of keys passed to find method");

            IQueryable<T> keyQuery = this.AsQueryable<T>();
            for (int i = 0; i < keyValues.Length; i++)
            {
                var x = i; // nested linq
                keyQuery = keyQuery
                   .Where(entity => _keyProperties[x].GetValue(entity, null).Equals(keyValues[x]));
            }

            return keyQuery.SingleOrDefault();
        }

        private void GetKeyProperties()
        {
            _keyProperties = new List<PropertyInfo>();
            PropertyInfo[] properties = typeof(T).GetProperties();
            foreach (PropertyInfo property in properties)
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    if (attribute is KeyAttribute)
                    {
                        _keyProperties.Add(property);
                    }
                }
            }
        }

Now thirdly I wanted the FakeDbSet to act like the database and use an identity column for properties that are ints and marked with the [Key] attribute. I made these changes here

private int _identity = 1;

private void GenerateId(T entity)
{
     // If non-composite integer key
     if (_keyProperties.Count == 1 && _keyProperties[0].PropertyType == typeof(Int32))
         _keyProperties[0].SetValue(entity, _identity++, null);
}

  public T Add(T item)
  {
      GenerateId(item);
      _data.Add(item);
      return item;
 }

Now of course this is being done in the Add method not the commit method as the database would. For my purposes this makes no difference. If however you want the key generation to be done on commit then you need to keep an un-comitted list inside of the FakeDbSet and then when commit is called you would iterate the list generating id’s for each element and then adding them to the ‘comitted’ list.

Here is the new FakeDbSet implementation


public class FakeDbSet<T> : IDbSet<T> where T : class
    {
        private readonly HashSet<T> _data;
        private readonly IQueryable _query;
		private int _identity = 1;
        private List<PropertyInfo> _keyProperties;

        private void GetKeyProperties()
        {
            _keyProperties = new List<PropertyInfo>();
            PropertyInfo[] properties = typeof(T).GetProperties();
            foreach (PropertyInfo property in properties)
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    if (attribute is KeyAttribute)
                    {
                        _keyProperties.Add(property);
                    }
                }
            }
        }

		private void GenerateId(T entity)
		{
            // If non-composite integer key
            if (_keyProperties.Count == 1 && _keyProperties[0].PropertyType == typeof(Int32))
                _keyProperties[0].SetValue(entity, _identity++, null);
		}

        public FakeDbSet(IEnumerable<T> startData = null)
        {
            GetKeyProperties();
			_data = (startData != null ? new HashSet<T>(startData) : new HashSet<T>());
            _query = _data.AsQueryable();
        }

        public virtual T Find(params object[] keyValues)
        {
            if (keyValues.Length != _keyProperties.Count)
                throw new ArgumentException("Incorrect number of keys passed to find method");

            IQueryable<T> keyQuery = this.AsQueryable<T>();
            for (int i = 0; i < keyValues.Length; i++)
            {
                var x = i; // nested linq
                keyQuery = keyQuery.Where(entity => _keyProperties[x].GetValue(entity, null).Equals(keyValues[x]));
            }

            return keyQuery.SingleOrDefault();
        }

        public T Add(T item)
        {
            GenerateId(item);
            _data.Add(item);
            return item;
        }

        public T Remove(T item)
        {
            _data.Remove(item);
            return item;
        }

        public T Attach(T item)
        {
            _data.Add(item);
            return item;
        }

        public void Detach(T item)
        {
            _data.Remove(item);
        }

        Type IQueryable.ElementType
        {
            get { return _query.ElementType; }
        }

        Expression IQueryable.Expression
        {
            get { return _query.Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _query.Provider; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        public T Create()
        {
            return Activator.CreateInstance<T>();
        }

        public ObservableCollection<T> Local
        {
            get
            {
                return new ObservableCollection<T>(_data);
            }
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            return Activator.CreateInstance<TDerivedEntity>();
        }
    }

Hope this code is useful to someone else ūüôā

Advertisements

EF 4.1 DbContext Issue : Manually opening and closing the database connection

As I’m sure many of you are aware the Entity Framework will create and close database connections automatically when needed. This is great most of the time, however when we want to manually configure the connection for performance or to perform a list of actions within a transaction we don’t want the entity framework to automatically close our connection.

I’ve found an issue where I’m trying to manually manage my DbContext connection and the DbContext API does not want to let me.
(I’m using Sql Server 2005 and am trying to avoid transaction promotion to the DLC which means I want to do all of my queries on the same connection).

In ObjectContext land, when I call ObjectContext.Connection.Open() I am manually opening the connection and the documentation states on MSDN that this connection will NOT be closed until I call the Close() method or dispose of the context.

It seems calling DbContext.Database.Connection.Open() does not give the same results. When called I watch the context close and reopen for each query. Below is the code that I am trying to write that presents the problem.

DbContext version:


            dbContext.Database.Connection.Open();
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                  // perform a list of queries
                 // the connection will close
                 scope.Complete();
                 dbContext.Database.Connection.Close();
            }

ObjectContext version:


            (dbContext as IObjectContextAdapter).ObjectContext.Connection.Open();
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                  // perform a list of queries
                 // The connection will not close!
                 scope.Complete();
                 (dbContext as IObjectContextAdapter).ObjectContext.Connection.Close();
            }

So the fix for now is to get the ObjectContext from your DbContext. But can someone explain what the difference is and is this by design?

Faking DbContext in Entity Framework 4.1 with a generic repository

Update 30/11/2011: FakeDbSet implementation update Please see the new and improved FakeDbSet Here

Update 16/06/2011:  Added step (2) description of how to implement Set<>() method in your original DbContext so that it returns IDbSet<>. Also added SaveChanges() to expose the context as a unit of work. + A little reorganisation.

Faking of the new Entity Framework 4.1 DbContext can be done quite simply by following these steps:

1. Create a common interface for your particular DbContext type.

I’m using a generic repository so my interface only needs to implement the Set method. But you could of course expose all your collections through this interface.

    public interface IMainModuleContext
    {
        IDbSet<Person> People { get; set; } // My collections...
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        void SaveChanges();
    }

Notice how our DbSet collections IDbSet instead of DbSet. This is because we will use an in-memory representation of the DbSet collection called FakeDbSet which implements IDbSet.

If you are exposing all of your collections and using model-first this could be generated with a T4 Template to save development time. Ensure your real DbContext implements this interface, and that your repository will take a IMainModuleContext instead of the concrete type.

2. Now lets make sure our original context (mine is called MainModuleContext) is implementing this interface. Example of the code to do this is below:

public partial class MainModuleContext : DbContext, IMainModuleContext
{
    public IDbSet<Person> People { get; set; }
    public MainModuleContext() : base() {}
    public IDbSet<TEntity> Set<TEntity>() where T : class
    {
        return base.Set<TEntity>();
    }

    public void SaveChanges()
    {
        base.SaveChanges();
    }
    // Other methods
}

Notice our properties must return IDbSet instead of DbSet. This is easy since the EF team have included the IDbSet interface for us.

3. Now we will create a fake dbset (an in-memory representation of a dbset)

    public class FakeDbSet<T> : IDbSet<T> where T : class
    {
        private HashSet<T> _data;

        public FakeDbSet()
        {
            _data = new HashSet<T>();
        }

        public virtual T Find(params object[] keyValues)
        {
            throw new NotImplementedException();
        }

        public T Add(T item)
        {
            _data.Add(item);
            return item;
        }

        public T Remove(T item)
        {
            _data.Remove(item);
            return item;
        }

        public T Attach(T item)
        {
            _data.Add(item);
            return item;
        }

        public void Detach(T item)
        {
             _data.Remove(item);
        }

        Type IQueryable.ElementType
        {
            get { return _data.AsQueryable().ElementType; }
        }

        Expression IQueryable.Expression
        {
            get { return _data.AsQueryable().Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _data.AsQueryable().Provider; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        public T Create()
        {
            return Activator.CreateInstance<T>();
        }

        public ObservableCollection<T> Local
        {
            get
            {
            return new ObservableCollection<T>(_data);
            }
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            return Activator.CreateInstance<TDerivedEntity >();
        }
    }

4. Now implement your fake context. The only tricky thing here is the Set method needs to use reflection to find the property we are after.


    public partial class FakeMainModuleContext : IMainModuleContext
    {
        public IDbSet<Person> People { get; set; }
        public IDbSet<T> Set<T>() where T : class
        {
            foreach (PropertyInfo property in typeof(FakeMainModuleContext).GetProperties())
            {
                if (property.PropertyType == typeof(IDbSet<T>))
                    return property.GetValue(this, null) as IDbSet<T>;
            }
            throw new Exception("Type collection not found");
        }

        public void SaveChanges()
        {
             // do nothing (probably set a variable as saved for testing)
        }

        public FakeMainModuleContext()
        {
            // Set up your collections
            People = new FakeDbSet
            {
                new Person() { FirstName = "Brent" }
            };
        }

You can now swap out your DbContext with a FakeDbContext for unit testing.