简体   繁体   中英

Entity Framework Core using CurrentValues.SetValues to update entity properties from other entity failing due to primary key on entity

Please see my original SO question here - Entity Framework Core - efficient way to update Entity that has children based on JSON representation of entity being passed in via Web API

This question details that I'm building an API using .NET Core, Entity Framework Core and PostgreSQL. I am trying to bring in an object via the API, convert it to a type of Customer entity, then use it to update an existing Customer entity so basically take all the modified properties that have changed between the original and update, and generate an update statement.

This has failed, due to the below error:

System.InvalidOperationException: The property 'CustomerInternalId' on entity type 'Customer' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.

I am unclear however, how I can perform the update while asking .NET Core / EF Core to just keep the primary key CustomerInternalId of the existing record?

Obviously I don't want to change the primary key, that's probably the one thing I don't ever want to update. It's all the other properties that may be changed by the incoming data.

Here's my sample method (it's half way through testing so there's bits of logging etc in there as I'm trying to figure this out).

    /// <summary>
    /// Update customer record - still working on this
    /// </summary>
    /// <param name="customerPayload"></param>
    public static void UpdateCustomerRecord(CustomerPayload customerPayload)
    {
        try
        {
            var updateCustomer = customerPayload.Convert(customerPayload);
            Console.WriteLine($"Update customer created from payload");

            using (var loyalty = new loyaltyContext())
            {
                Console.WriteLine($"Using context to get db customer by mca id {updateCustomer.McaId}");
                var customer = loyalty.Customer
                    .Include(c => c.ContactInformation)
                    .Include(c => c.Address)
                    .Include(c => c.MarketingPreferences)
                    .Include(c => c.ContentTypePreferences)
                    .Include(c => c.ExternalCards)
                    .Where(c => c.McaId == updateCustomer.McaId).First();

                var cu = (from c in loyalty.Customer
                    where c.McaId == updateCustomer.McaId
                    select c).ToList();

                Console.WriteLine($"Customer guid from linq query {cu.First().CustomerInternalId}");

                Console.WriteLine(
                    $"db customer Id {customer.CustomerInternalId} incoming customer Id {updateCustomer.CustomerInternalId}");

                loyalty.Entry(customer).CurrentValues.SetValues(updateCustomer);
                loyalty.Entry(customer).State = EntityState.Modified;
                Console.WriteLine($"customer last name: {customer.LastName}");
                loyalty.SaveChanges();
                //TODO expand code to cover scenarios such as an additional address on an udpate
            }
        }
        catch (ArgumentNullException e)
        {
            Console.WriteLine(e);
            throw new CustomerNotFoundException();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex}");
        }
    }
}

You can use the following generic replacement method which will skip shadow (similar to the original) and key properties:

public static void UpdateProperties(this DbContext context, object target, object source)
{
    foreach (var propertyEntry in context.Entry(target).Properties)
    {
        var property = propertyEntry.Metadata;
        // Skip shadow and key properties
        if (property.IsShadowProperty() || (propertyEntry.EntityEntry.IsKeySet && property.IsKey())) continue;
        propertyEntry.CurrentValue = property.GetGetter().GetClrValue(source);
    }
}

(for EF Core 2.x replace IsShadowProperty() with IsShadowProperty )

Now you can simply use this instead:

loyalty.UpdateProperties(customer, updateCustomer);

Side note: the following line

loyalty.Entry(customer).State = EntityState.Modified;

should be removed. The customer is already tracked, hence the EF will intelligently update only the modified properties (or no update at all), while this forces updating all properties regardless of being changed or not.

This was simpler than I'd realised. All I needed to do was update the 'incoming' Customer to have the same internal Id as the existing customer record, then no update, no issue, The code is so simple it seems pointless to post here: but just in case:

/// <summary>
/// Update customer record - still working on this
/// </summary>
/// <param name="customerPayload"></param>
public static void UpdateCustomerRecord(CustomerPayload customerPayload)
{
    try
    {
        var updateCustomer = customerPayload.Convert(customerPayload);

        using (var loyalty = new loyaltyContext())
        {
            var customer = loyalty.Customer
                .Include(c => c.ContactInformation)
                .Include(c => c.Address)
                .Include(c => c.MarketingPreferences)
                .Include(c => c.ContentTypePreferences)
                .Include(c => c.ExternalCards)
                .Where(c => c.McaId == updateCustomer.McaId).First();

            updateCustomer.CustomerInternalId = customer.CustomerInternalId;

            loyalty.Entry(customer).CurrentValues.SetValues(updateCustomer);
            loyalty.SaveChanges();
        }
    }
    catch (ArgumentNullException e)
    {
        Console.WriteLine(e);
        throw new CustomerNotFoundException();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{ex}");
    }
}

With emphasis on:

            updateCustomer.CustomerInternalId = customer.CustomerInternalId;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM