简体   繁体   English

Entity Framework Core 使用 CurrentValues.SetValues 从其他实体更新实体属性,由于实体上的主键而失败

[英]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请在此处查看我的原始 SO 问题 - Entity Framework Core - 基于通过 Web API 传入的实体的 JSON 表示来更新具有子实体的有效方法

This question details that I'm building an API using .NET Core, Entity Framework Core and PostgreSQL.这个问题详细说明了我正在使用 .NET 核心、实体框架核心和 PostgreSQL 构建 API。 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.我正在尝试通过 API 引入 object,将其转换为Customer实体类型,然后使用它来更新现有Customer实体,因此基本上采用在原始和更新之间更改的所有修改属性,并生成更新陈述。

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. System.InvalidOperationException:实体类型“Customer”上的属性“CustomerInternalId”是键的一部分,因此无法修改或标记为已修改。 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.要使用标识外键更改现有实体的主体,首先删除依赖项并调用“SaveChanges”,然后将依赖项与新主体关联。

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?但是,我不清楚如何在要求 .NET Core / EF Core 只保留现有记录的主键CustomerInternalId时执行更新?

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 ) (对于 EF Core 2.x 将IsShadowProperty()替换为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. customer已被跟踪,因此 EF 将智能地仅更新已修改的属性(或根本不更新),而这会强制更新所有属性,无论是否更改。

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:我需要做的就是更新“传入”客户,使其具有与现有客户记录相同的内部 ID,然后没有更新,没有问题,代码非常简单,在这里发布似乎毫无意义:但以防万一:

/// <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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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