【问题标题】:Generic class with Id c#具有 Id c# 的泛型类
【发布时间】:2017-11-27 12:55:19
【问题描述】:

我不确定这是否可行,但我已经开始了一个新项目,并且正在尝试清理我使用 DbContext 的方式。我正在尝试通过使用泛型来做到这一点。 第一部分很简单。我像这样创建一个 Repository 类:

public class Repository<T> where T : class
{
    protected readonly DbContext _dbContext;
    protected readonly DbSet<T> _dbEntitySet;

    public Repository(InteractiveChoicesContext dbContext)
    {
        _dbContext = dbContext;
        _dbEntitySet = dbContext.Set<T>();
    }

    /// <summary>
    /// Gets all the entities
    /// </summary>
    /// <param name="includes">Option includes for eager loading</param>
    /// <returns></returns>
    public IQueryable<T> List(params string[] includes)
    {
        IQueryable<T> query = _dbEntitySet;
        foreach (var include in includes)
            query = query.Include(include);
        return query;
    }

    /// <summary>
    ///     Creates an entity
    /// </summary>
    /// <param name="model"></param>
    public void Create(T model) => _dbEntitySet.Add(model);

    /// <summary>
    ///     Updates an entity
    /// </summary>
    /// <param name="model"></param>
    public void Update(T model) => _dbEntitySet.Update(model);

    /// <summary>
    /// Removes an entity
    /// </summary>
    /// <param name="model"></param>
    public void Remove(T model) => _dbContext.Entry<T>(model).State = EntityState.Deleted;

    /// <summary>
    /// Saves the database context changes
    /// </summary>
    /// <returns></returns>
    public async Task SaveChangesAsync()
    {
        await _dbContext.SaveChangesAsync();
    }

现在我想创建一个可以使用所有 CRUD 方法的通用 Service 类。我试着像这样创建它:

public class Service<T> : Service<T, int> where T : class
{
    public Service(InteractiveChoicesContext dbContext) : base(dbContext)
    {
    }
}

public class Service<T, TKey>: Repository<T> where T: class
{
    public Service(InteractiveChoicesContext dbContext) : base(dbContext)
    {
    }
    public virtual async Task<IEnumerable<T>> ListAsync() => await List().ToListAsync();
    public virtual async Task<T> GetAsync(TKey id) => await List().SingleOrDefaultAsync(m => m.Id == id);
    public virtual async Task<T> CreateAsync(T model)
    {
        Create(model);
        await SaveChangesAsync();
        return model;
    }
    public virtual async Task<T> UpdateAsync(T model)
    {
        Update(model);
        await SaveChangesAsync();
        return model;
    }
    public virtual async Task<bool> DeleteAsync(TKey id)
    {
        var model = await GetAsync(id);
        Remove(model);
        await SaveChangesAsync();
        return true;
    }
}

我使用 TKey 来指定原始类型,但问题在于 GetAsync 方法。显然此时它不知道对象是什么,所以它不会编译。我收到此错误:

“T”不包含“Id”的定义,并且找不到接受“T”类型的第一个参数的扩展方法“Id”(您是否缺少 using 指令或程序集引用?)

现在我知道这是因为我没有说 T 是什么。 看看 IdentityFramework 如何处理这个问题,他们使用 IUser,我也可以这样做,但这意味着我的所有实体都必须实现这个接口。 如果我创建了这样的界面:

public interface IEntity<out TKey>
{
    TKey Id { get; }
}

然后更新我所有的实体来实现这样的接口:

public class Question : IEntity<int>
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
    [Required, MaxLength(100)] public string Text { get; set; }
    public bool MultipleChoice { get; set; }

    public OutcomeGroup Group { get; set; }
    public IEnumerable<Answer> Answers { get; set; }
}

这似乎行得通。我想我只有一个问题。 这是最好的方法吗?

【问题讨论】:

  • 如果您的代码有效并且您只是询问这是否是“最佳”方式,那么问题可能属于codereview.stackexchange.com(尽管最好先检查他们的规则)
  • 是的,实现接口来访问泛型变量的特定属性通常是最好的方法。请注意,EF DbSet 具有 Find(和 FindAsync)方法,该方法接受表示实体主键的对象数组,可以在您的情况下使用它而不是 SingleOrDefault
  • 啊,谢谢你:)
  • 因此,如果您的表有一百万条记录,并且您计划使用 GetAsync 获取一条记录,那么您首先将所有百万条记录放到本地内存中,然后将它们全部丢弃,除了一条?考虑尽可能长时间地保持它 IQueryable。让您的调用者决定何时执行您的查询,以便他可以有效地连接 IQueryable 扩展
  • 我将它保留为 IQueryable 直到它到达 API 端点

标签: c# entity-framework generics


【解决方案1】:

你可以拥有这样的实体:

public interface IEntity<out TKey>
{
    TKey Id { get; }
}

public class Question<TPrimaryKey> : IEntity<int>
{
     public TPrimaryKey Id { get; set; }
}

然后你的存储库是这样的:

public class Repository<TEntity, TPrimaryKey> where TEntity: IEntity<TPrimaryKey>
{
    protected readonly DbContext _dbContext;
    protected readonly DbSet<TEntity> _dbEntitySet;

    public Repository(InteractiveChoicesContext dbContext)
    {
        _dbContext = dbContext;
        _dbEntitySet = dbContext.Set<TEntity>();
    }
}

在我看来,这是“通用”的最佳方式。

【讨论】:

  • DbSet&lt;T&gt; T 是什么?这不会编译。我想应该是TEntity
  • @Fabjan 是的,应该是 TEntity。
【解决方案2】:

嗯,你需要一些方法来指向 ID / key 属性。

需要探索的一些想法:

  • 正如您已经编写的那样,创建一个您的所有实体都将实现的接口,该接口表示 Id 属性
  • (奖励)您可以拥有一个实现上述接口的基实体类,并具有所需的 EF 注释 ([DatabaseGenerated(DatabaseGeneratedOption.Identity)]) 以使事情变得更容易
  • GetAsync 方法中使用反射按名称搜索 Id 属性(显然代码不会像现在这样简单)
  • 创建自定义或使用现有属性(例如,KeyAttribute)并将其添加到您的实体中以标记 Id 属性,并与之前的想法一样使用反射来查找属性

在所有这些解决方案中,我会选择第一个或第二个,因为它们似乎最“清晰”。如果您决定进行反思,那么我会选择第二个(您有一个键定义属性)-但这仍然会迫使您遍历所有实体并进行更改。如果您有很多实体,那么基于文本的属性搜索可能是最快的 - 但显然任何没有名为“Id”的属性的实体都会导致问题。

显然 YMMV 取决于您实际想要完成的任务。请注意,您可以尝试结合反射解决方案(按名称查找,如果未找到则按键属性搜索)。

还请记住,虽然反射是一种强大的工具,但它会对性能产生影响。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-01
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多