【问题标题】:Is it possible to call a method on all generic properties in a class?是否可以在类中的所有泛型属性上调用方法?
【发布时间】:2014-09-15 07:52:42
【问题描述】:

我有一个有很多 IDbSet<SomeClass> 类型属性的类:

public InMemoryContext : IContext
{
    public IDbSet<ClassA> ClassASet { get; set; }
    public IDbSet<ClassB> ClassBSet { get; set; }
    [...]

    public void SaveChanges()
    {
        //TODO: this is relevant part
    }
}

所有这些属性都在构造函数中实例化:

public InMemoryContext
{
    ClassASet = new InMemoryDbSet<ClassA>();
    [...]
}

InMemoryDbSet 对此问题有一种相关方法:

public class InMemoryDbSet<T> : IDbSet<T> where T : class
{
    public void SaveChanges()
    {
         [...]
    }
}

我想获取所有IDbSet 的属性并循环调用SaveChanges(),因此我不需要第二次重复所有集合名称。

我尝试了反射,但由于我的属性是通用的,我无法让它工作。我尝试使ClassAClassB 派生自相同的通用接口,但仍然没有运气。

是否真的可以在不明确指定所有集合的情况下做到这一点?我应该改变什么来达到这个结果?

我想象伪代码是这样的:

public void SaveChanges()
{
    foreach(var set in GetDbSetsFromClass(this))
    {
        set.SaveChanges();
    }
}

我尝试了反射:

public void SaveChanges()
    {
        SaveChangesCalled = true;

        var properties = GetType().GetProperties().Where(p => p.PropertyType.IsInterface && typeof(IDbSet<IEntity>).IsAssignableFrom(p.PropertyType)).Cast<InMemoryDbSet<IEntity>>();

        foreach (var property in properties)
        {
            CallSaveChanges(property);
        }
    }

而且列表是空的 公共布尔 SaveChangesCalled { 获取;放; }

    private static void CallSaveChanges<T>(InMemoryDbSet<T> set) where T : class
    {
        set.SaveChanges();
    }

编辑:公共属性必须在 InMemoryContext 类的顶层保持可见,因为这受到定义 EF 上下文的接口的限制。

【问题讨论】:

  • “我尝试了反射” - 反射应该可以工作,你尝试了什么?
  • 集合有SaveChanges方法吗?我在文档中找不到它? IDbSet&lt;T&gt; 发布接口定义是什么。
  • InMemoryDbSet 具有代码中显示的内容。 IDbSet 没有。这是我认为最难的部分。

标签: c# generics reflection covariance


【解决方案1】:

您的代码不起作用,因为IDbSet&lt;Class&gt; 不能分配给IDbSet&lt;IEntity&gt;,因为IDbSet&lt;T&gt; 不是协变的。

你想要的是找到类型的通用定义IDbSet&lt;&gt;的属性。

var properties = typeof (InMemoryContext)
    .GetProperties()
    .Where(p => p.PropertyType.IsGenericType &&
                p.PropertyType.GetGenericTypeDefinition() == typeof (IDbSet<>));

要调用您的 CallSaveChanges 方法,您需要:

foreach(var proparty in properties)
{
    var value = property.GetValue(this);
    var entityType = property.PropertyType.GetGenericArguments().First();

    var callSaveChanges = this.GetType()
                              .GetMethod("CallSaveChanges", BindingFlags.NonPublic | BindingFlags.Static);

    var constructedCallSaveChanges = callSaveChanges.MakeGenericMethod(entityType );

    constructedCallSaveChanges.Invoke(null, BindingFlags.NonPublic | BindingFlags.Static, null, new object[]{ value }, CultureInfo.InvariantCulture);
}

话虽如此,我认为这种情况不需要使用反射。

【讨论】:

  • 我并不局限于反思。如果有更好的方法我可以接受。
  • 我尝试了您的解决方案,它正在寻找属性,但我怎样才能在它们上调用SaveChanges()?转换为InMemoryDbSet&lt;IEntity&gt; 不起作用(事实上,如果可能的话,我宁愿不使用IEntity)。 SaveChanges() 方法是在 InMemoryDbSet 上定义的,不管它是不是泛型类型。
  • @Episodex 哦,所以有一个非通用的InMemoryDbSet?只需将 values 转换为 InMemoryDbSet 即可。
  • 不,抱歉让您感到困惑,InMemoryDbSet 是通用的(有问题的定义),但我想我不应该关心 T 的类型是什么,如果我只想调用 SaveChanges () 在上面。 IDbSet&lt;&gt; 之类的东西,但不能用于强制转换。
  • @Episodex 不幸的是,您将不得不跳上铁圈才能完成这项工作。我已经更新了我的答案。
【解决方案2】:

对于一个简单的解决方案,您可以尝试这样的方法

public InMemoryContext
{
    public Dictionary<string, object> allSets = new Dictionary<string, object>();
    public InMemoryContext()
    {
        allSets.Add("classA", new InMemoryDbSet<ClassA>());
        allSets.Add("classB", new InMemoryDbSet<ClassB>());
        [...]
    }
    public IDbSet<ClassA> ClassASet { get
    {
        return allSets["classA"];
    }
    set
    {
        allSets["classA"] = Value
    }
    }
    [...]

    public void SaveChanges()
    {
    //TODO: this is relevant part
    foreach(var kvp in allSets)
    {
        kvp.Value.SaveChanges();
    }
    }
}

有些事情可能是错误的,因为它只是在我的脑海中......但你应该明白这个想法

【讨论】:

  • 我也想过这个,但是因为这是InMemoryContext,它必须看起来完全像EF上下文(它必须满足一些接口)。我没有明确提出这个问题,以免造成更多混乱,抱歉。
猜你喜欢
  • 2019-12-15
  • 2020-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 2023-04-03
  • 2023-04-10
相关资源
最近更新 更多