继承自DbSet<T>,旨在添加属性。

5
有没有一种方法可以从DbSet类继承?我想添加一些新属性,就像这样:
public class PersonSet : DbSet<Person>
{
    public int MyProperty { get; set; }
}

但我不知道如何在我的DbContext中实例化它。
public partial MyContext : DbContext
{
    private PersonSet _personSet; 
    public PersonSet PersonSet
    {
        get 
        {
            _personSet = Set<Person>(); // Cast Error here
            _personSet.MyProperty = 10;
            return _personSet;
        }
    }
}

我该如何实现这个目标?

_personSet = Set<PersonSet>();? - Scottie
不行,因为我想从Person类中获取一个dbset。我的实体是Person,而不是PersonSet... - Alexandre TRINDADE
是的,你可以从它继承。问题在于你的上下文返回的是一个DbSet而不是PersonSet。必须将其实现为_personSet = new PersonSet{ MyProperty = 10 }; - Arturo Martinez
但是我无法使用这个来分配DBSet... - Alexandre TRINDADE
4个回答

5
我已找到适合我的答案。我在上下文中将我的DbSet属性声明为派生接口,例如:
IDerivedDbSet<Customer> Customers { get; set; }
IDerivedDbSet<CustomerOrder> CustomerOrders { get; set; }

我的实现包括一个私有的IDbSet,它在构造函数中被赋值,例如:

 public class DerivedDbSet<T> : IDerivedDbSet<T> where T : class
 {
    private readonly IDbSet<T> _dbSet;

    public DerivedDbSet(IDbSet<T> dbSet)
    {
        this._dbSet = dbSet;
    }
 ...
 }

我的派生DbContext接口的实现隐藏了Set<>()方法,如下所示:

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;
    }

派生的DbContext会返回一个新构造的IDerivedSet,或者选择在字典中缓存引用。在派生的DbContext中,我从构造函数调用一个方法,该方法使用类型反射遍历DbContext的属性,并使用自己的Set方法分配值/引用。请参见此处:

 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));
                }
            }
        }
    }

这对我来说非常有帮助。我的上下文类具有可导航的集合属性,这些属性是我的集合类型的实现,该类型继承了IDbSet接口。这意味着我可以在我的集合类型上包含查询方法,以便查询可以进行单元测试,而不是使用Queryable类的静态扩展。(Queryable方法由我的自定义方法直接调用)。


当我尝试这样做时,调用DerivedDbSet,Add方法时会出现错误:“实体类型TestEntity不是当前上下文的模型的一部分。” 有什么想法我做错了什么吗? 我的构造函数如下: - Martien de Jong
public DerivedDbContext(string connString) : base(connString) { AssignDerivedSets(); } - Martien de Jong
啊,我找到原因了:在我的OnModelCreating中,我必须为每个实体调用modelBuilder.Entity<EntityType>()(使用反射)。 - Martien de Jong

5
一种解决方案是创建一个实现IDbSet接口并将所有操作委托给真正的DbSet实例的类,这样就可以存储状态。
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
}

然后在您的 DbContext 中,为您的自定义 DbSet 添加一个 getter:
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;
        }
    }

}

3

我通过使用另一个变量来实例化“常规”DbSet来解决了这个问题。

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

这样,Entity Framework 可以识别实体,但我仍然可以使用自己的 DbSet 类。

这很不幸。 - Erutan409
@Erutan409 我最终设法用另一种方式解决了这个问题。以下代码演示了这一点:https://github.com/martiendejong/Rejuvenate/blob/master/ChangePublishingDbContextExample/Models/Domain/GameContext.cs 你需要挖掘一些东西才能弄清楚我是如何得到这个结果的。 - Martien de Jong

2
我知道这已经很老了,OP可能已经不关心了,但我自己也在思考同样的问题。EF会在运行时填充MyContext中的DbSets。我创建了一个MyDbSet<T>,它继承自DbSet<T>,并用我的派生类替换了MyContext中对DbSet<T>的所有引用。然而,运行程序失败,无法实例化任何属性。
接下来,我尝试将属性设置为IDbSet<T>,因为DbSet<T>实现了这个接口,这是可以行得通的。
进一步调查发现,DbSet的构造函数是受保护和内部的(受保护的构造函数仍然调用内部的构造函数)。所以微软让你自己编写版本变得非常困难。你可以通过反射访问内部构造函数,但EF可能无法构造你的派生类。
我建议编写一个扩展方法将功能插入到DbSet对象中,但如果你想存储状态,你就被卡住了。

我认为答案可能在DbModelBuilder.Configurations.Add中,并且它可能会在执行OnModelCreating时分配值。我想在OnModelCreating中使用类型反射来创建我的派生类型的新实例,该实例接受IDbSet作为构造函数。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接