【问题标题】:How can I get the type of T from the type of T?如何从 T 的类型中获取 T 的类型?
【发布时间】:2017-02-24 06:29:26
【问题描述】:

我正在尝试将我的组织使用的框架重写为 .Net Core;特别是此时的通用存储库。 我被困在以下问题上。

我们有一个 BaseEntity 定义如下:

public abstract class BaseEntity<T> : IBaseEntity<T>
{
    [Key]
    public virtual T Id { get; set; }
}

它继承自接口:

public interface IBaseEntity<T>
{
    [Key]
    T Id { get; set; }
}

我这样定义一个实体:

public class Employee : BaseEntity<int>
{
    public string OfficeBureau { get; set; }
    public string Supervisor { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Notes { get; set; }

    // TODO:
    // public virtual ICollection<Case> Cases { get; set; }
}

应用上下文:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }

    public DbSet<Employee> Employees { get; set; }
}

回购接口:

public interface IGenericRepository<T>
    where T : BaseEntity<T>
{
    IQueryable<T> GetAll();     // No need to async IQueryable
    Task<T> GetAsync(int id);
    Task<T> InsertAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(T entity);

    #region Possible TODOs:
    // Should this one go in the Service Layer?
    // Task<IEnumerable<T>> FindBy(Expression<Func<T, bool>> predicate);

    //Task AddRange(IEnumerable<T> entities);
    //Task RemoveRange(IEnumerable<T> entities);
    #endregion

    Task SaveAsync();
}

回购实施:

public class GenericRepository<T> : IGenericRepository<T>
    where T: BaseEntity<T>
{
    private readonly ApplicationDbContext _context;
    private DbSet<T> _entities;

    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _entities = context.Set<T>();
    }

    // No need to async IQueryable
    public IQueryable<T> GetAll() => _entities.AsQueryable();

    public Task<T> GetAsync(int id)
    {
        throw new NotImplementedException();
    }

现在看看签名:

public Task<T> GetAsync(int id)

这是我的困境。 将所有实体设置为从 BaseEntity 继承,以便我们在此处搜索硬编码的 int 类型的 GetById 或 GetAsync 时可以动态获得 int 或 guid 的 id 有什么意义?

这是一种常见的模式吗?这是代码原作者的短板吗?还是我在这种模式中遗漏了什么?
如果这是一个缺点,有没有办法通过反射和搜索来动态获取实体 id 的类型?如果是这样,它会影响性能吗?

我是否应该只删除 BaseEntity 上 id 类型的泛型并强制框架的用户始终使所有实体在使用该框架的整个应用程序中具有相同类型的 ID?

除了通用回购的主要目的之外。不必为每个实体使用相同的方法编写 repo。 任何建议在这里表示赞赏。

【问题讨论】:

  • 您的标题似乎与您的问题不匹配。
  • EntityFramework 团队自己处理了同样的问题,他们公开了 DbContext.Find() 方法(实际上是“通过 id 获取”),该方法期望 object id 作为参数(参见 github.com/aspnet/EntityFramework/blob/dev/src/… )。
  • 为了澄清 GenericRepo 中的 T 是否指的是 Employee,而 Employee 派生自 BaseEntity 其中 T 是实体 ID 的类型,那么我说 T 表示“Employee : BaseEntity”嵌套在 GenericRepository 中。这是两个不同的T。我玩弄了“GenericRepository 然后是 BaseEntity。对于问题的命名感到抱歉。在查看上面的 EF 团队评论时,很有趣,但现在看到应用它或它将如何解决我的问题问题。
  • @Sam,当idobject 时,应用它的方式就是await return _entities.FindAynsc(id)
  • 我无法理解这一点。我是否必须更改上述任何代码?还是会按原样工作?

标签: c# generics asp.net-core-mvc


【解决方案1】:

您的问题是您对“T”是什么感到困惑。您已经完成了大部分工作,但是您在代码中途切换了 T 的“上下文”。一开始它是指“Id”的类型,然后你将它设为“Model”的类型

试试这个。我将进行一些重命名以使其更清晰,而不是使用 T。

您的基础实体应如下所示:

public interface IBaseEntity<Key>
{
    [Key]
    Key Id { get; set; }
}

员工本质上应该是一样的:

public class Employee : BaseEntity<int>
{
    public string OfficeBureau { get; set; }
    public string Supervisor { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Notes { get; set; }

    // TODO:
    // public virtual ICollection<Case> Cases { get; set; }
}

现在您的通用存储库。现在仔细看下面的代码。这就是你出错的地方。

public interface IGenericRepository<T, Key>
    where T : BaseEntity<Key>
{
    IQueryable<T> GetAll();     // No need to async IQueryable
    Task<T> GetAsync(Key id);
    Task<T> InsertAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(T entity);
}

所以你注意到我现在传递了两个泛型类型。一个是此存储库正在调用的模型类型,另一个是模型使用的密钥类型。

您的通用存储库应如下所示

public class GenericRepository<T, Key> : IGenericRepository<T, Key>
{
    private readonly ApplicationDbContext _context;
    private DbSet<T> _entities;

    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _entities = context.Set<T>();
    }

    // No need to async IQueryable
    public IQueryable<T> GetAll() => _entities.AsQueryable();

    public Task<T> GetAsync(Key id)
    {
        throw new NotImplementedException();
    }
}

然后当您创建一个实际的存储库时,它可能如下所示。

public class UserRepository : GenericRepository<User, int>, IGenericRepository<User, int>, IUserRepository
{
...
}

【讨论】:

  • 谢谢 - 我已经意识到这一点,并尝试完全按照您在此处所说的进行。我这样命名我的:“公共接口 IGenericRepository where T : BaseEntity”。但是现在一直遵循它。在我去使用 FindAsync 方法的仓库中,我认为你不能使用泛型作为传入参数的类型:我认为这会抱怨:“public Task GetAsync(TId id)”或“public Task GetAsync(Key id)" 但是,我会做更多的工作。有没有人有一个使用传入泛型类型定义 CRUD 方法的 repo 的完整示例?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-21
  • 1970-01-01
  • 2021-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多