Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
zinov opened this issue Feb 27, 2021 · 1 comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@zinov
Copy link

zinov commented Feb 27, 2021

How to load a related entity after calling AddAsync

I have a repository method that looks like this

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);
    }
}

Trying to insert an Order for example that looks like this, and only passing the StatusId and the TradingActionId makes the Add safe

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"
   };

the problem with this is that if I need to return the new entity with certain navigation properties. My following approach doesn't work but shows the idea of what I need to save the instance and at the same time return the child properties. The version I am using of EF Core is Microsoft.EntityFrameworkCore.SqlServer 3.1.4

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);
    }
}

Any suggestions of how to create the generic repo method and return the data needed without making another roundtrip to the database?

I posted the question as well on Stackoverflow link

@roji
Copy link
Member

roji commented Mar 3, 2021

First, EF Core doesn't currently allow you to batch your queries and updates applied via SaveChanges. #10879 is about exposing an API whereby users could perform multiple queries in a single roundtrip, and it may be extensible to include SaveChanges as well (I've added a note).

However, your code above raises some questions... Typically, after adding a new entity, there aren't yet any relationships to be loaded back from the database. What exactly is the scenario in which you want to insert a new entity and at the same time query back entities related to it?

Second, forgetting the AddAsync for a second... your code above loops over the include array, and performs an explicit load for each relationship. This means a database roundtrip per relationship, which is indeed quite inefficient. You can simply have a single query with multiple Includes, to load all the navigations in one go. This works particularly well with reference navigations, whereas for collection navigations it may produce the so-called "cartesian explosion" problem (see the docs). Using split query can mitigate this; at the moment split queries usually do a separate roundtrip per relationship (unless using MARS on SqlServer) - so would not be an improvement - but we plan to address that and batch in the future (#10878).

To summarize, I'm still missing a more general understanding of what you're trying to do, and why you think you need to add and retrieve relationships in a single batch etc.

@AndriySvyryd AndriySvyryd added the closed-no-further-action The issue is closed and no further action is planned. label Mar 8, 2021
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

4 participants