使用 EF Core 按 Id 检索实体的通用方法

Generic method to retrieve an entity by Id with EF Core

我创建了一个 API,其中我在数据库中有几个对象(例如卖家、产品等),我为这些对象中的每一个提供了一个 enpoint api 来获得给定的使用其 Id(始终为 Guid 类型)从数据库中获取对象。我注意到在我处理业务逻辑的每个 class 中,我重复相同的代码:

//GetOneSellerQueryHandler class
...
var sellerDbItem = await Context.Seller.Where(x => x.Id == request.Id)
                .SingleOrDefaultAsync();
var seller = Mapper.Map<SellerDto>(sellerDbItem );
...

//GetOneProductQueryHandler class
...
var productDbItem = await Context.Product.Where(x => x.Id == request.Id)
                .SingleOrDefaultAsync();
var product = Mapper.Map<ProductDto>(productDbItem);
...

使用通用方法创建抽象 class 以通过传递的 id 从数据库中获取元素是否有意义?

另外,这个泛型方法的实现是否正确?

//Abstract class
public async Task<F> GetElementById<T,F>(T obj, Guid id) where T : class
{
   T dbItem = (T) Convert.ChangeType(obj switch
   {
      Seller => await Context.Seller.Where(x => x.Id == id).SingleAsync(),
      Product=> await Context.Product.Where(x => x.Id == id).SingleAsync(),
      _=>throw new NotImplementedException()
   }, typeof(T));

   return Mapper.Map<F>(dbItem);
}


//GetOneProductQueryHandler class
...
var productDbItem = await GetElementById<Product, ProductDto>(new Product(), request.Id)
...
//GetOneSellerQueryHandler class
...
var sellerDbItem = await GetElementById<Seller, SellerDto>(new Seller(), request.Id)
...

Does it make sense create abstract class with generic method to get element from db by passed id?

我的建议:不要害怕重复自己。

通用减少行数...但它会损害可维护性。

Moreover, is this implementation of the generic method correct?

不完全是,因为类型是硬编码的。此外, filter Context.XXX.Where(x => x.Id == id).SingleAsync() 的代码是重复的。这结合了所有默认值、更多的行和更少的可维护性。

EF Core 使用 Queryable,然后您可以生成一个通用表达式,例如:

public static class QueryableExtensions
{
    public static Task<T>? GetById<T>(this IQueryable<T> query, Guid id)
    {
        // This expression is lambad : e => e.Id == id
        var parameter = Expression.Parameter(typeof(T));
        var left = Expression.Property(parameter, "Id");
        var right = Expression.Constant(id);
        var equal = Expression.Equal(left, right);
        var byId = Expression.Lambda<Func<T, bool>>(equal, parameter);
        
        return query.SingleAsync(byId);
    }
}

//Use
var seller = await context.Sellers.GetById(Guid.NewGuid());
var product = await context.Products.GetById(Guid.NewGuid());

表达力很强,但是很难

通常我为数据库实体声明基础class/interface以访问共享属性并基于它们创建通用方法

using AutoMapper;
using Microsoft.EntityFrameworkCore;

public interface IEntity
{
    public Guid Id { get; set; }
}

public class Seller : IEntity
{
    public Guid Id { get; set; }
}

public class Product : IEntity
{
    public Guid Id { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Seller> Sellers { get; set; }
}

public class Repository
{
    private readonly ApplicationDbContext ctx;

    public Repository(ApplicationDbContext ctx)
    {
        this.ctx = ctx;
    }

    public Task<T> GetById<T>(Guid id) where T : class, IEntity
    {
        return ctx.Set<T>().SingleAsync<T>(x => x.Id == id);
    }
}

public class Handler<Entity, Dto> where Entity : class, IEntity
{
    private readonly Repository repository;
    private readonly IMapper mapper;

    public Handler(Repository repository, IMapper mapper)
    {
        this.repository = repository;
        this.mapper = mapper;
    }

    public async Task<Dto> GetDtoById(Guid id)
    {
        var entity = await repository.GetById<Entity>(id);

        var dto = mapper.Map<Dto>(entity);

        return dto;
    }
}