为具有 Table Per Hierarchy 模型设计(MVCCore + EF 2.2)的实体实施 REST API 的最佳建议

Best advice to Implement REST API for entities with Table Per Hierarchy model design (MVCCore + EF 2.2)

我有几个派生的 classes ShippableDownloadable 继承自相同的基础 class Product 并且它们每个都有自己的属性:

    public abstract class Product : Entity<Guid>
    {
        public ProductType ProductType{ get; set; }

        public string Name { get; set; }
    }

    public class Downloadable : Product
    {
        public Guid DownloadId { get; set; }
    }

    public class Shippable : Product
    {
        public Guid ShippingInfo { get; set; }
    }

我为我的 DataTable 使用了 EF Core TBH 设计,因此此实体的所有属性都存储在 table 中:

            builder.Entity<Product>(
                b =>
                {
                    b.ToTable(
                        options.TablePrefix + "Products",
                        options.Schema
                    );

                    b.HasKey(
                        x => x.Id
                    );

                    b.HasDiscriminator<ProductType>(
                         nameof(Product.ProductType)
                     )
                     .HasValue<Downloadable>(
                         ProductType.Downloadable
                     )
                     .HasValue<Shippable>(
                         ProductType.Shippable
                     );
                }

我的项目是 NLayered DDD,所以我有一个应用程序层,它为 UI/REST API 方法生成业务逻辑并包含应用程序服务和 DTO。

我的问题是应用这个的最佳实践是什么。您对这个选项的看法:

1- 每个派生的业务服务 class 和派生的 DTO

2- 只有一项服务可以为所有服务提供共享 DTO(包含派生的 classes

的可为空属性

3- 您的建议

提前致谢。

我不完全确定你在找什么,但从 API 的角度来看,我会使用一个抽象的产品控制器,然后从中为每个特定的产品类型派生一个控制器:

[Route("api/[controller]")]
[ApiController]
public abstract class ProductController<TProduct, TProductDTO> : ControllerBase
    where TProduct : Product, new()
    where TProductDTO : class, new()
{
}

public class DownloadableController : ProductController<Downloadable, DownloadableDTO>
{
}

public class ShippableController : ProductController<Shippable, ShippableDTO>
{
}

然后,您使用通用类型 TProductTProductDTO 在抽象产品控制器上构建所有端点。例如,您可能有如下内容:

[HttpPost("")]
public async Task<ActionResult<TProductResource>> Create(TProductDTO dto)
{
    var product = _mapper.Map<TProduct>(dto);
    _context.Add(product);
    await _context.SaveChangesAsync();
    return CreatedAt("Get", new { product.Id }, _mapper.Map<TProductDTO>(product));
}

由于泛型类型,同样的方法适用于所有派生控制器,无需重新定义甚至覆盖,尽管您可以通过添加 virtual 关键字允许它被覆盖。不过,添加占位符方法通常会更好。例如,您可以执行以下操作:

private virtual Task BeforeCreateAsync(TProduct product, TProductDTO dto) =>

Task.CompletedTask;

private virtual Task AfterCreateAsync(TProduct product, TProductDTO dto) =>

Task.CompletedTask;

然后包装您的 SaveChangesAsync 调用:

_context.Add(product);
await BeforeCreateAsync(product, dto);
await _context.SaveChangesAsync();
await AfterCreateAsync(product, dto);

然后,在您派生的控制器中,您可以覆盖这些以在附加功能中存根:

private override async Task BeforeCreateAsync(Downloadable product, DownloadableDTO dto)
{
    // do something specific to downloadable products
}

这样,您的大部分逻辑仍然是独立的。您可以添加尽可能多的对您的应用程序有意义的占位符方法。