You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#泛型接口实现疑问:如何通过接口返回不同数据库实体类型

通用数据检索接口的实现方案探讨

首先可以明确地说:将接口定义为泛型接口ISqlTradeDataRetriever<T>,然后让实现类指定具体实体类型(比如SqlCommodityDataRetriever : ISqlTradeDataRetriever<Commodity>)的方案是完全可行的,而且这是比你最初写法更合理、类型更安全的方向。

为什么你最初的写法会报错?

你最初的接口是非泛型的,但方法是泛型的,这种写法会让编译器无法在编译期确认Commodity类型可以安全转换为泛型参数T——哪怕你在代码里做了typeof(T) == typeof(Commodity)的判断,编译器也不会为这种运行时检查做类型推断,所以会抛出转换错误。虽然你可以通过强制类型转换(比如return (Task<T>)(object)_context.Commodities.FirstOrDefaultAsync(...))绕开编译错误,但这种写法非常不安全,容易引发运行时类型转换异常,而且代码可读性极差,完全不推荐。

泛型接口的正确实现示例

先定义带约束的泛型接口(添加where T : class约束可以让EF的DbSet操作更顺畅):

public interface ISqlTradeDataRetriever<T> where T : class
{
    Task<T> GetByPrimaryKeysAsync(string symbol, string date);
    Task<ICollection<T>> GetAllAsync(string symbol);
    Task<ICollection<T>> GetDataInDateRange(string symbol, KeyValuePair<DateTime, DateTime> dateRange);
}

然后实现针对Commodity的检索类:

public class SqlCommodityDataRetriever : ISqlTradeDataRetriever<Commodity>
{
    private readonly IBatlGroupWebContext _context;

    public SqlCommodityDataRetriever(IBatlGroupWebContext context)
    {
        _context = context;
    }

    public async Task<Commodity> GetByPrimaryKeysAsync(string symbol, string date)
    {
        var tradeDate = Convert.ToDateTime(date);
        return await _context.Commodities.FirstOrDefaultAsync(m => m.Symbol == symbol && m.TradeDate == tradeDate);
    }

    public async Task<ICollection<Commodity>> GetAllAsync(string symbol)
    {
        return await _context.Commodities.Where(m => m.Symbol == symbol).ToListAsync();
    }

    public async Task<ICollection<Commodity>> GetDataInDateRange(string symbol, KeyValuePair<DateTime, DateTime> dateRange)
    {
        return await _context.Commodities
            .Where(m => m.Symbol == symbol && m.TradeDate >= dateRange.Key && m.TradeDate <= dateRange.Value)
            .ToListAsync();
    }
}

这种写法不仅没有语法错误,而且类型安全,代码逻辑清晰,每个实现类只负责对应实体的检索逻辑。

进一步减少代码冗余的优化方案

如果你的4个实体都有Symbol和TradeDate这类通用属性,我们可以通过抽象基类+统一实体接口的方式,把通用逻辑抽离出来,彻底消除重复代码:

  1. 先定义一个所有交易数据实体都要实现的接口,统一通用属性:
public interface ITradeDataEntity
{
    string Symbol { get; set; }
    DateTime TradeDate { get; set; }
}

让你的Commodity和其他3个实体都实现这个接口:

public class Commodity : ITradeDataEntity
{
    public string Symbol { get; set; }
    public DateTime TradeDate { get; set; }
    // 其他实体特有属性
}
  1. 创建抽象基类,实现通用的检索逻辑:
public abstract class BaseSqlTradeDataRetriever<T> : ISqlTradeDataRetriever<T> 
    where T : class, ITradeDataEntity
{
    protected readonly IBatlGroupWebContext _context;
    protected readonly DbSet<T> _dbSet;

    protected BaseSqlTradeDataRetriever(IBatlGroupWebContext context)
    {
        _context = context;
        _dbSet = context.Set<T>(); // 利用EF的Set<T>方法获取对应DbSet
    }

    public async Task<T> GetByPrimaryKeysAsync(string symbol, string date)
    {
        var tradeDate = Convert.ToDateTime(date);
        return await _dbSet.FirstOrDefaultAsync(m => m.Symbol == symbol && m.TradeDate == tradeDate);
    }

    public async Task<ICollection<T>> GetAllAsync(string symbol)
    {
        return await _dbSet.Where(m => m.Symbol == symbol).ToListAsync();
    }

    public async Task<ICollection<T>> GetDataInDateRange(string symbol, KeyValuePair<DateTime, DateTime> dateRange)
    {
        return await _dbSet
            .Where(m => m.Symbol == symbol && m.TradeDate >= dateRange.Key && m.TradeDate <= dateRange.Value)
            .ToListAsync();
    }
}
  1. 每个实体的检索类只需要继承基类即可,不需要重复写通用逻辑:
public class SqlCommodityDataRetriever : BaseSqlTradeDataRetriever<Commodity>
{
    public SqlCommodityDataRetriever(IBatlGroupWebContext context) : base(context)
    {
    }

    // 如果Commodity有特有的检索需求,再在这里添加或重写方法即可
}

这样其他3个实体的检索类也可以用同样的方式实现,代码量会大幅减少,后期维护也更方便。

内容的提问来源于stack exchange,提问作者dinotom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:21:33