如何在调用 AddAsync 后加载相关实体而不再次往返数据库

How to load a related entity after call AddAsync without making another roundtrip to the database

如何在调用后加载相关实体AddAsync

我有一个类似这样的存储库方法

public virtual async Task<TEntity> AddAsync(TEntity entity)
{
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

    try
    {
        entity.CreatedOn = entity.UpdatedOn = DateTime.Now;
        var newEntity = await Entities.AddAsync(entity);
        var newEntityToRet = newEntity.Entity;
        _context.SaveChanges();
        
        return newEntityToRet;
    }
    catch (DbUpdateException exception)
    {
        //ensure that the detailed error text is saved in the Log
        throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
    }
}

尝试插入一个如下所示的订单,并且仅传递 StatusIdTradingActionId 使添加安全

public class Order
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
    public string CreatedBy { get; set; }
    public string UpdatedBy { get; set; }
    public DateTime? CreatedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public string Symbol { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
    public int StatusId { get; set; }
    public OrderStatus Status { get; set; }
    public int TradingActionId { get; set; }
    public TradingAction TradingAction { get; set; }
    public string Notes { get; set; }
}

var order = new Order
{
       TradingActionId = 1,
       StatusId = 1,
       Notes = source.Notes,
       Price = source.Price,
       Symbol = source.Symbol,
       Quantity = source.Quantity,
       CreatedOn = dateTimeNow,
       UpdatedOn = dateTimeNow,
       UpdatedBy = "test",
       CreatedBy = "test"
 };

问题在于,如果我需要 return 具有某些导航属性的新实体。我的以下方法不起作用,但显示了我需要保存实例的想法,同时 return 子属性。

public virtual async Task<TEntity> AddAsync(TEntity entity, string[] include = null)
{
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

    try
    {
        entity.CreatedOn = entity.UpdatedOn = DateTime.Now;
        var newEntity = await Entities.AddAsync(entity);
        var newEntityToRet = newEntity.Entity;
        _context.SaveChanges();

        if(include != null)
        {
            foreach (var navProp in include)
            {
                try
                {
                    var memberEntry = _context.Entry(newEntityToRet).Member(navProp);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }

                if (memberEntry is DbCollectionEntry collectionMember)
                    collectionMember.Load();

                if (memberEntry is DbReferenceEntry referenceMember)
                    referenceMember.Load();
            }
        }
        
        return newEntityToRet;
    }
    catch (DbUpdateException exception)
    {
        //ensure that the detailed error text is saved in the Log
        throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
    }
}

我使用的 EF Core 版本是 Microsoft.EntityFrameworkCore.SqlServer 3.1.4

关于如何创建通用回购方法和 return 所需数据而无需再次往返数据库的任何建议?

插入新实体后,您始终可以使用 Load()LoadAsync() 方法显式加载其相关实体。但是,即使它不执行典型的 LINQ method/query(就像您为获取一些相关数据而编写的那样),EF 也需要为每次显式加载向数据库提交新查询。因此,它确实不会为您节省任何数据库之旅。

由于您已经尝试在代码中使用 Load() 方法,我假设您的意图只是为了避免编写新的 LINQ 查询(需要获取相关数据) ,而不是避免数据库旅行。如果是这种情况,您可以尝试以下操作 -

public async Task<TEntity> AddAsync(TEntity entity, params string[] includes)
{
    entity.CreatedOn = entity.UpdatedOn = DateTime.Now;
    
    var newEntry = Entities.Add(entity);
    await _context.SaveChangesAsync();    // trip to database

    foreach (var navProp in includes)
    {
        if (newEntry.Navigation(navProp).Metadata.IsCollection())
        {
            await newEntry.Collection(navProp).LoadAsync();    // trip to database
        }
        else
        {
            await newEntry.Reference(navProp).LoadAsync();    // trip to database
        }
    }
    return newEntry.Entity;
}

你可以使用 -

var addedOrder = await orderRepository.AddAsync(order, "Status", "TradingAction");

通知-

  1. 我正在使用 params 传递一个或多个参数
  2. 要add/insert一个新实体使用AddAttach方法。除非你正在处理像 SequenceHiLo 这样的值生成器,否则你真的不需要使用 AddAsync 方法。详情 - AddAsync<TEntity>

类型安全的实现是 -

public async Task<TEntity> AddAsync(TEntity entity, params Expression<Func<TEntity, object>>[] includes)
{
    entity.CreatedOn = entity.UpdatedOn = DateTime.Now;
    
    var newEntry = Entities.Add(entity);
    await _context.SaveChangesAsync();    // trip to database

    foreach (var navProp in includes)
    {
        string propertyName = navProp.GetPropertyAccess().Name;

        if (newEntry.Navigation(propertyName).Metadata.IsCollection())
        {
            await newEntry.Collection(propertyName).LoadAsync();    // trip to database
        }
        else
        {
            await newEntry.Reference(propertyName).LoadAsync();    // trip to database
        }
    }
    return newEntry.Entity;
}

以便您可以传递导航属性,例如 -

var addedOrder = await orderRepository.AddAsync(order, p => p.Status, p => p.TradingAction);