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

4

5

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?

Faqir answered 6/12, 2013 at 15:42 Comment(4)
_personSet = Set<PersonSet>();?Qp
No, because I want to get a dbset from Person class. My entity is Person, not PersonSet...Faqir
Yes you can inherit from it. The problem is that your context is returns a DbSet and not a PersonSet. Have to implement it as _personSet = new PersonSet{ MyProperty = 10 };Multifarious
But I can't assign dbset with this ...Faqir
C
5

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).

Condemnatory answered 8/10, 2015 at 15:4 Comment(3)
When I try this I get the error 'The entity type TestEntity is not part of the model for the current context.' when calling the DerivedDbSet,Add method. Any idea what I'm doing wrong? My constructor looks like this:Cleghorn
public DerivedDbContext(string connString) : base(connString) { AssignDerivedSets(); }Cleghorn
Ah, I found the cause: In my OnModelCreating I had to call modelBuilder.Entity<EntityType>() for each entity (using reflection)Cleghorn
U
5

One solution is to create a class that implements IDbSet and delegates all operations to a real DbSet instance, so you can store state.

public class PersonSet : IDbSet<Person>
{
    private readonly DbSet<Person> _dbSet;

    public PersonSet(DbSet<Person> dbSet)
    {
        _dbSet = dbSet;
    }

    public int MyProperty { get; set; }

    #region implementation of IDbSet<Person>

    public Person Add(Person entity)
    {
        return _dbSet.Add(entity);
    }

    public Person Remove(Person entity)
    {
        return _dbSet.Remove(entity);
    }

    /* etc */
    #endregion
}

Then in your DbContext, put a getter for your Custom DbSet:

public class MyDbContext: DbContext
{
    public DbSet<Person> People { get; set; }

    private PersonSet _personSet;
    public PersonSet PersonSet
    {
        get 
        {
            if (_personSet == null)
                _personSet = new PersonSet( Set<Person>() );

            _personSet.MyProperty = 10;

            return _personSet;
        }
        set
        {
            _personSet = value;
        }
    }

}
Universe answered 9/4, 2015 at 3:32 Comment(0)
C
5

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).

Condemnatory answered 8/10, 2015 at 15:4 Comment(3)
When I try this I get the error 'The entity type TestEntity is not part of the model for the current context.' when calling the DerivedDbSet,Add method. Any idea what I'm doing wrong? My constructor looks like this:Cleghorn
public DerivedDbContext(string connString) : base(connString) { AssignDerivedSets(); }Cleghorn
Ah, I found the cause: In my OnModelCreating I had to call modelBuilder.Entity<EntityType>() for each entity (using reflection)Cleghorn
C
3

I solved this using another variable to instantiate the "regular" DbSet.

    private DbSet<Person> _persons { get; set; }
    public PersonDbSet<Person> Persons { get { return new PersonDbSet(_persons); } }

This way entityframework recognizes the Entity but I can still use my own DbSet class.

Cleghorn answered 7/5, 2017 at 19:17 Comment(2)
Well, this is unfortunate.Nahum
@Nahum I think I managed to solve this in another way eventually. The following code demonstrates this: github.com/martiendejong/Rejuvenate/blob/master/… You will have to do some digging to figure out what I did to get this result.Cleghorn
P
2

I know this is really old and the OP has probably moved on but I was just wondering the same thing myself. EF populates the DbSets inside your MyContext at run time.

I just created MyDbSet<T> that inherits from DbSet<T> and the replaced all references to DbSet<T> with my derived class in MyContext. Running my program failed to instantiate any of the properties.

Next I tried setting the properties to IDbSet<T> since DbSet<T> implements this interface. This DOES work.

Investigating further, the constructors for DbSet are protected and internal (the protected one calls the internal one anyway). So MS have made it pretty hard to roll your own version. You may be able to access the internal constructors through reflection but chances are that EF will not construct your derived class anyway.

I would suggest writing an extension method to plug the functionality into the DbSet object, however you're stuck if you want to store state.

Princessprinceton answered 30/7, 2014 at 10:58 Comment(1)
I thought the answer might lie in DbModelBuilder.Configurations.Add and that it might assign the values when executing OnModelCreating. I'm tempted to use type reflection in OnModelCreating to do create a new instance of my derived type, which accepts an IDbSet as a constructor.Condemnatory

© 2022 - 2024 — McMap. All rights reserved.