在我使用 dotnet 3.1 构建的 API 中,我有实体“Quotation”的数据访问模型,如下所示:
public class QuotationDao : BaseEntity
{
public double Amount { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public string Status { get; set; }
public Guid CustomerId { get; set; }
public CustomerDao Customer { get; set; }
public virtual ICollection<QuoteItemDao> Items { get; set; }
}
当我创建实体“QuoteItem”的新实例时,我想将“Quotation”实体的“amount”属性增加 1。并更新数据库。
BaseController.cs的实现如下所示。
public class BaseController<TDao, TCreateRq, TUpdateRq, TResponse> : ControllerBase
where TDao : BaseEntity where TResponse : BaseResponse
{
private readonly IRepositoryWrapper _repositoryWrapper;
private readonly IRepositoryBase<TDao> _repository;
private readonly IMapper _mapper;
public BaseController(IRepositoryWrapper repositoryWrapper, IRepositoryBase<TDao> repository, IMapper mapper)
{
_repositoryWrapper = repositoryWrapper;
_repository = repository;
_mapper = mapper;
}
[HttpGet]
public virtual async Task<ActionResult<PagedResponse<TResponse>>> GetAll([FromQuery] StringQueryRequest request)
{
var paginationFilter = _mapper.Map<PaginationFilter>(request);
var pagedResponse = await _repository.ToPagedList
(_repository.FindAll(request.SearchString), paginationFilter);
var mapPagination = pagedResponse.MapPagination<TResponse, TDao>(_mapper);
return mapPagination.HandleToResponse();
}
[HttpGet("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> GetById(Guid id)
{
var result = await GetDtoById(id);
return result.HandleGetToResponse();
}
[HttpPost]
public virtual async Task<ActionResult<Response<TResponse>>> Create([FromBody] TCreateRq request)
{
var entity = _mapper.Map<TDao>(request);
_repository.Create(entity);
await _repositoryWrapper.SaveAsync();
var baseResponse = _mapper.Map<TResponse>(entity);
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
[HttpPut("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> UpdateById([FromRoute] Guid id,
[FromBody] TUpdateRq updateRq)
{
var getResult = await GetDtoById(id);
if (getResult.IsNone)
{
return getResult.HandleGetToResponse();
}
var value = await _repository.FindById(id);
//override the current DB objet from the values received from the request
//Not replacing the full object from the requests as some fields may be missing from the update RQ model (eg user password)
var entity = _mapper.Map(updateRq, value);
_repository.Update(entity);
await _repositoryWrapper.SaveAsync();
var baseResponse = _mapper.Map<TResponse>(entity);
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
[HttpDelete("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> DeleteById([FromRoute] Guid id)
{
var getResult = await GetDtoById(id);
if (getResult.IsNone)
{
return getResult.HandleGetToResponse();
}
var baseResponse = getResult.ValueUnsafe();
var entity = _mapper.Map<TDao>(baseResponse);
_repository.Delete(entity);
await _repositoryWrapper.SaveAsync();
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
private async Task<Option<TResponse>> GetDtoById(Guid id)
{
var findById = await _repository.FindById(id);
return _mapper.Map<TResponse>(findById);
}
}
我无法找到实现此目的的方法,如果有人可以提供帮助,我将不胜感激。
这就是依赖纯粹通用方法的问题。 Generic意味着无论你传入什么,它们都可以在Generic类的范围内被视为identical。也就是说,作为基本实现,您所拥有的还不错,但您将遇到一些限制,因为通用实现在利用 EF 所能带来的优势方面代表了“最低公分母”。
您的问题可以通过围绕聚合根构建控制器并通过这些根执行操作来解决。在使用相关实体时,您会发现的麻烦是您的控制器需要了解这些关系,以确保急切地加载所需的详细信息以供使用。您可以利用控制器和存储库的通用基类,但实际的控制器和存储库的范围将围绕聚合根(例如引用)。添加报价项将通过其根(Quotation)完成,而不是使用 QuotationItemController 和 QuotationItemRepository 之类的东西。 QuotationController 的目标是支持可以加载引用及其各自项目的方法,并具有像 AddQuotationItem 这样的方法。然后,AddQuotationItem 将按 ID 加载报价,急切加载项目,验证 QuotationItem 的输入,创建 QuotationItem,将其附加到 quote.Items,并在保存更改之前增加报价的金额。
这里的关键是不要考虑将所有内容都放入通用模式中,而是在需要控制器或存储库等相同元素时利用通用模式。
其他一些提示:您使用哪种映射器?如果是 Automapper,则在投影到视图模型时考虑使用
ProjectTo<TDestination>(config)
而不是 Map<TDestination>()
。这里的好处是,投影在整个 IQueryable
范围内工作,而不是引入获取实体并依赖相关实体进行急切加载或唤醒延迟加载的性能巨魔的开销。如果像 FindAll
这样的方法返回 IEnumerable<TEntity>
,那么这将是有问题的,但如果它返回 IQueryable<TEntity>
那么你应该被设置。如果 FindAll 返回 IEnumerable
,则将 FindAll 传递给 ToPagedList 之类的内容不会节省性能损失。当从视图模型转换回 DTO 时,如果您的实体被跟踪,您应该避免使用 Update
并仅使用 Automapper 的
mapper.Map(updateRq, value)
会将值从源复制到目标。如果 Destination 是被跟踪的实体,则只需调用 SaveChanges
,EF 将针对实际更改的值构建适当的 UPDATE 语句(如果任何值实际发生更改)。 (Update
将为所有值生成 UPDATE 语句,即使实际上没有任何更改。)
那个
Update
模式更多的是使用的情况:
var dao = mapper.Map<TEntity>(dto);
context.Update(dao);
context.SaveChanges();
我绝不会建议使用它:
var dao = context.Entities.Single(x => x.Id == dto.Id);
mapper.Map(dto, dao);
context.SaveChanges();
当后面的模式验证传入的 dto 具有相应的实体时,应将 Automapper 配置为仅根据配置的规则跨允许的属性进行复制,然后
SaveChanges
仅在实际发生任何更改时才会生成有效的 UPDATE SQL 语句。