atiyar
atiyar

Reputation: 8305

Update parent and child (in one-to-one) with a generic method

I came across this question, and liked how the generic update for one-to-many is implemented.

I tried to mimic it to implement a one-to-one version for myself but could not be totally successful. Following is the result of my struggle -

public async Task<int> UpdateAsync<T>(T entity, params Expression<Func<T, object>>[] navigations) where T : EntityBase
{
    var dbEntity = await _DbCtx.FindAsync<T>(entity.Id);

    var entry = _DbCtx.Entry(dbEntity);
    entry.CurrentValues.SetValues(entry);

    foreach (var nav in navigations)
    {
        string propertyName = nav.GetPropertyAccess().Name;        
        
        // Problem #01 //
        // if possible, I'd like to avoid this reflection in favor of EF Core MetaData?
        var child = (EntityBase)entity.GetType().GetProperty(propertyName).GetValue(entity);
        if (child == null)
            continue;            // if the client-sent model doesn't have child, skip

        var referenceEntry = entry.Reference(propertyName);
        await referenceEntry.LoadAsync();

        var dbChild = (EntityBase)referenceEntry.CurrentValue;
        if (dbChild == null)
        {
            // Problem #02 //
            // if the existing entity doesn't have child, the client-sent child will be assigned.
            // but I could not figure out how to do this
        }
        else
        {
            _DbCtx.Entry(dbChild).CurrentValues.SetValues(child);
        }
    }

    return await _DbCtx.SaveChangesAsync();
}

I have marked the problems as Problem #01 and Problem #02 in code comments above. Any suggestion, solution will be appreciated. Thanks.

Edit :
Alternatively, if you think there is a better, more efficient way of doing the same thing I'm trying to do above, please share your knowledge.

Upvotes: 0

Views: 345

Answers (1)

R. Zanel
R. Zanel

Reputation: 118

For Problem #01, I couldn't find equivalent of GetCollectionAccessor, but one way I can think of solving it using EF Core metadata, would be calling the Entry method in the disconnected Entity:

var entry = _context.Entry(dbEntity);
entry.CurrentValues.SetValues(entry);

var disconnectedEntry = _context.Entry(entity); // new

foreach (var nav in navigations)
{
    string propertyName = nav.GetPropertyAccess().Name;
    var navigationChild = disconnectedEntry.Navigation(propertyName).CurrentValue; // new
}

Bear in mind that in every Entry method call EF Core will try to DetectChanges. As this verification is not necessary for this Entity, you can save some performance disabling the AutoDetectChanges as suggested in this issue.

Now for Problem #02, you can just assign the child Entity to the ReferenceEntry CurrentValue, like this:

        var referenceEntry = entry.Reference(propertyName);
        await referenceEntry.LoadAsync();

        var dbChild = (EntityBase)referenceEntry.CurrentValue;
        if (dbChild == null)
        {
           referenceEntry.CurrentValue = navigationChild; // new
        }
        else
        {
            _DbCtx.Entry(dbChild).CurrentValues.SetValues(child);
        }

I hope it can help you. If some problem arises let me know!

Edit

I also suggest you to read about the TrackGraph method. Depending on how your entities work, maybe everything could be done with this method with a couple of lines.

Upvotes: 1

Related Questions