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

Jam*_*son 8 c# entity-framework entity-framework-core .net-core

请在此处查看我原来的 SO 问题 - Entity Framework Core - effective way to update Entity that haschildren based on JSONrepresentation ofEntity being pass in via Web API

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

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

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

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

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

这是我的示例方法(测试已经进行了一半,所以当我试图弄清楚这一点时,其中有一些日志记录等)。

    /// <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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*son 6

这比我想象的要简单。我所需要做的就是更新“传入”客户,使其具有与现有客户记录相同的内部 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}");
    }
}
Run Code Online (Sandbox Code Playgroud)

重点是:

            updateCustomer.CustomerInternalId = customer.CustomerInternalId;
Run Code Online (Sandbox Code Playgroud)


Iva*_*oev 6

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

public static void UpdateProperties(此 DbContext 上下文,对象目标,对象源)
{
    foreach (context.Entry(target).Properties 中的 var propertyEntry)
    {
        var 属性 = propertyEntry.Metadata;
        // 跳过阴影和关键属性
        if (property.IsShadowProperty() || (propertyEntry.EntityEntry.IsKeySet && property.IsKey())) 继续;
        propertyEntry.CurrentValue = property.GetGetter().GetClrValue(source);
    }
}

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

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

忠诚度.UpdateProperties(客户, updateCustomer);

旁注:以下行

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

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