使用反射在方法调用中获取正确的返回值

3

我在我的类中有以下通用方法,它作为仓储模式工作:

public DbSet<T> GetAll<T>() where T : class
{
  return dbContext.Set<T>();
}

现在,我想获取数据库中属于实现特定接口(IChangeTrackingEntity)的实体类的所有实体列表。目前有约10个特定的表/类符合这个条件,但我不想添加10个硬编码调用这些表的代码,所以我希望使用反射来完成它(也可能是实现该接口的类将来会改变,我不想记得在这里进行更改,并使代码相互依赖)。

以下是可以正常工作但我不想要的代码示例:

var result = new List<IChangeTrackingEntity>();
using ( var repository = new DbRepository())
{
  result.AddRange( repository.GetAll<FirstTypeThatImplementsInterface>() );
  result.AddRange( repository.GetAll<SecondTypeThatImplementsInterface>() );
  result.AddRange( repository.GetAll<ThirdTypeThatImplementsInterface>() );
  result.AddRange( repository.GetAll<FourthTypeThatImplementsInterface>() );
  result.AddRange( repository.GetAll<FifthTypeThatImplementsInterface>() );
}
return result;

我已经很接近了,但是我无法让最后一部分正常工作,即将Invoke的结果转换回正确的类型。目前我的代码如下:

var result = new List<IChangeTrackingEntity>();
var method = typeof (DbRepository).GetMethod("GetAll");
using ( var repository = new DbRepository())
{
  foreach (var p in typeof(AnchorDbContext).GetProperties())
  {
    if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
    {
      var pType =  p.PropertyType.GetGenericArguments()[0];
      if (pType.GetInterface("IChangeTrackingEntity") != null)
      {
        var genericMethod = method.MakeGenericMethod(new[] {pType});
        result.AddRange(genericMethod.Invoke(repository, null) as DbSet<IChangeTrackingEntity>);
      }
    }
  }
  return result;
}

上述问题是Invoke调用返回一个object,我需要将其转换为基本的DbSet<pType>
在我的当前代码中,genericMethod.Invoke(repository, null) as DbSet<IChangeTrackingEntity>返回null,表明我无法按照自己的意愿进行强制类型转换,因此我需要具体的返回值类型而不是接口。
有什么想法可以实现这一点吗?

嗯,你的代码对我来说没有意义。根据实现方式,你正在从数据库中获取与此接口匹配的所有实体。你知道这会导致性能问题吗? - usr
是的,我想从数据库中获取所有这些条目,因为它们将在另一个数据库中被镜像,但并不是数据库中的所有其他条目。 - Øyvind Bråthen
3个回答

3
尝试将类型转换为 IEnumerable<IChangeTrackingEntity>。由于协变性和逆变性原则,这应该可行。

成功了。看来我需要学习一下协变性/逆变性。所以它在IEnumerable中可以工作,因为它是一个接口,但在DbSet上不能工作,因为它不是一个接口?感谢您的努力。 - Øyvind Bråthen
与接口无关。它适用于IEnumerable,因为您只能取出元素。DbSet允许您放入一些随机类,包括从IChangeTrackingEntity派生的类。这就是为什么它被禁止的原因。 - usr
但这不是协变导致的吗?我的意思是,我有一个继承自B的类A。然后我不能设置DbSet<B> = DbSet<A>,但我可以设置IEnumerable<B> = IEnumerable<A>。如果我只是从IEnumerable中获取元素,那又有什么关系呢? - Øyvind Bråthen
是的,这是由于协变性。我建议你在这个主题上多读一些。实践中很少遇到这个问题。 - usr

1

我不太了解这个具体的问题,但你似乎正在从DbSet<T> where T : IChangeTrackingEntity转换为DbSet<IChangeTrackingEntity>。 这称为协变或逆变(我总是对它们感到困惑...),只有当DbSet<>是一个接口时才有效。 因此,在这里转换无法工作。 如果可以,请使用等效的接口,或者创建一个通用方法,该方法接受DbSet,其中T:IChangeTrackingEntity,并以某种方式返回DbSet<IChangeTrackingEntity>。 如果没有人在我之前回答(在这个网站上不太可能:P),我会尝试解决如何完成这个方法,并发布一个答案。


是的,我猜这是一个协变性/逆变性问题。在foreach循环的第一轮中返回的实际列表是DbSet<MobileUnit>,即使MobileUnit实现了IChangeTrackingEntity,协变性(或逆变性,我经常混淆它们:))也不会发挥作用,并且强制转换被视为无效。 - Øyvind Bråthen
看起来你的想法是正确的,usr也确认了。当我将其转换为IEnumerable而不是DbSet时,它可以正常工作,因为这时协变性/逆变性会发挥作用。所以现在它可以工作了:D。感谢你的努力。 - Øyvind Bråthen
我很高兴 :) 只是我不知道 DbSet<> 是什么 :P - GregRos

0

我想你需要看一下这个问题

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

1
问题不在于参数,我已经处理了那些。问题在于返回类型。返回类型是DbSet<SomeType>,但我不知道如何进行强制转换。 - Øyvind Bråthen

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