首页 > 解决方案 > Entity Framework Core 使用 CurrentValues.SetValues 从其他实体更新实体属性,由于实体上的主键而失败

问题描述

请在此处查看我的原始 SO 问题 - Entity Framework Core - 基于通过 Web API 传入的实体的 JSON 表示来更新具有子实体的有效方法

这个问题详细说明了我正在使用 .NET Core、Entity Framework Core 和 PostgreSQL 构建 API。我试图通过 API 引入一个对象,将其转换为一种Customer实体,然后使用它来更新现有Customer实体,因此基本上采用在原始和更新之间发生变化的所有修改属性,并生成更新语句。

由于以下错误,此操作失败:

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

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

显然我不想更改主键,这可能是我不想更新的一件事。传入数据可能会更改所有其他属性。

这是我的示例方法(它已完成测试的一半,因此在我试图弄清楚这一点时,那里有一些日志记录等)。

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

标签: c#entity-framework.net-coreentity-framework-core

解决方案


您可以使用以下通用替换方法,该方法将跳过阴影(类似于原始)和关键属性:

public static void UpdateProperties(this DbContext context, object target, object source)
{
    foreach (var propertyEntry in context.Entry(target).Properties)
    {
        var 属性 = propertyEntry.Metadata;
        // 跳过阴影和关键属性
        if (property.IsShadowProperty() || (propertyEntry.EntityEntry.IsKeySet && property.IsKey())) 继续;
        propertyEntry.CurrentValue = property.Getter().GetClrValue(source);
    }
}

(对于 EF Core 2.x 替换IsShadowProperty()IsShadowProperty

现在您可以简单地使用它:

忠诚度.UpdateProperties(客户,更新客户);

旁注:以下行

忠诚度.Entry(客户).State = EntityState.Modified;

应该被删除。已customer被跟踪,因此 EF 将智能地仅更新已修改的属性(或根本不更新),而这会强制更新所有属性,无论是否更改。


推荐阅读