Search code examples
entity-frameworkdbcontextinheritancedbset

Inherits from DbSet<T> with the purposes to add property


Is there a way to inherits from DbSet? I want to add some new properties, like this:

public class PersonSet : DbSet<Person>
{
    public int MyProperty { get; set; }
}

But I don't know how to instantiate it in my DbContext

public partial MyContext : DbContext
{
    private PersonSet _personSet; 
    public PersonSet PersonSet
    {
        get 
        {
            _personSet = Set<Person>(); // Cast Error here
            _personSet.MyProperty = 10;
            return _personSet;
        }
    }
}

How can I achieve this?


Solution

  • I have found an answer that works for me. I declare my DbSet properties as my derived interface in my context, e.g.:

    IDerivedDbSet<Customer> Customers { get; set; }
    IDerivedDbSet<CustomerOrder> CustomerOrders { get; set; }
    

    My implementation includes a private IDbSet which which is assigned in the constructor e.g.:

     public class DerivedDbSet<T> : IDerivedDbSet<T> where T : class
     {
        private readonly IDbSet<T> _dbSet;
    
        public DerivedDbSet(IDbSet<T> dbSet)
        {
            this._dbSet = dbSet;
        }
     ...
     }
    

    My implementation of a derived DbContext interface hides the Set<>() method like so:

    new public IDerivedSet<TEntity> Set<TEntity>() where TEntity : class
        {
            //Instantiate _dbSets if required
            if (this._dbSets == null)
            {
                this._dbSets = new Dictionary<Type, object>();
            }
    
            //If already resolved, return stored reference
            if (this._dbSets.ContainsKey(typeof (TEntity)))
            {
                return (IDerivedSet<TEntity>) this._dbSets[typeof (TEntity)];
            }
            //Otherwise resolve, store reference and return 
            var resolvedSet = new GlqcSet<TEntity>(base.Set<TEntity>());
            this._dbSets.Add(typeof(TEntity), resolvedSet);
            return resolvedSet;
        }
    

    The derived DbContext returns a newly constructed IDerivedSet or picks it's reference cached in a Dictionary. In the derived DbContext I call a method from the constructor which uses type reflection to go through the DbContexts properties and assigns a value/reference using it's own Set method. See here:

     private void AssignDerivedSets()
        {
            var properties = this.GetType().GetProperties();
            var iDerivedSets =
                properties.Where(p =>
                    p.PropertyType.IsInterface &&
                    p.PropertyType.IsGenericType &&
                    p.PropertyType.Name.StartsWith("IDerivedSet") &&
                    p.PropertyType.GetGenericArguments().Count() == 1).ToList();
    
            foreach (var iDerivedSet in iDerivedSets)
            {
                var entityType = iDerivedSet.PropertyType.GetGenericArguments().FirstOrDefault();
                if (entityType != null)
                {
                    var genericSet = this.GetType().GetMethods().FirstOrDefault(m =>
                        m.IsGenericMethod &&
                        m.Name.StartsWith("Set") &&
                        m.GetGenericArguments().Count() == 1);
                    if (genericSet != null)
                    {
                        var setMethod = genericSet.MakeGenericMethod(entityType);
                        iDerivedSet.SetValue(this, setMethod.Invoke(this, null));
                    }
                }
            }
        }
    

    Works a treat for me. My context class has navigable set properties of my set type that implements a derived interface inheriting IDbSet. This means I can include query methods on my set type, so that queries are unit testable, instead of using the static extensions from the Queryable class. (The Queryable methods are invoked directly by my own methods).