实体框架核心:无法使用嵌套值对象更新实体 [英] Entity Framework Core: Fail to update Entity with nested value objects

查看:122
本文介绍了实体框架核心:无法使用嵌套值对象更新实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有值对象的实体,而该值对象具有另一个值对象.我的问题是,当与值对象一起更新实体时,带有父值对象的实体会得到更新,而子值对象却没有. 注意,我使用了最新版本的Entity Framework Core 2.1.0-rc1-final 这是父实体Employee

I have an entity that has a value object and this value object has another value object. My issue is that when updating the entity along with the value objects, the entity with the parent value object get updated but the child value object didn't. note, i used latest version of Entity Framework Core 2.1.0-rc1-final this is the parent entity Employee

public class Employee : Entity
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
    public Address Address { get; private set; }
}

这是父值对象Address

and this is the parent value object Address

public class Address : ValueObject<Address>
{
    private Address() { }

    public Address(string street, string city, string state, string country, string zipcode, GeoLocation geoLocation)
    {
        Street = street;
        City = city;
        State = state;
        Country = country;
        ZipCode = zipcode;
        GeoLocation = geoLocation;
    }

    public string Street { get; private set; }
    public string City { get; private set; }
    public string State { get; private set; }
    public string Country { get; private set; }
    public string ZipCode { get; private set; }
    public GeoLocation GeoLocation { get; private set; }
}

这是子值对象GeoLocation

and this is the child value object GeoLocation

public class GeoLocation
{
    private GeoLocation()
    {

    }

    public GeoLocation(decimal longitude, decimal latitude)
    {
        Latitude = latitude;
        Longitude = longitude;
    }
    public Decimal Longitude { get; private set; }
    public Decimal Latitude { get; private set; }
}

在更新员工时,我首先从数据库中获取它,然后使用从用户界面获得的新值来更改Address属性.

and when updating the employee, i first get it from database, then change Address property using the new value obtained from user interface

var employee = _repository.GetEmployee(empId);
employee.SetAddress(newAddress);

和SetAddress方法

and the SetAddress method

public void SetAddress(Address address)
{
    Guard.AssertArgumentNotNull(address, nameof(address));
    Address = address;
}

推荐答案

根据这张EF Core GitHub票证,您必须直接更新child/nested/owned类型属性,以使其正确跟踪.该功能原本应该在EF 2.1中得到修复(目前仅可作为发布候选者使用),但可能并未削减.在2.0.3中,他们将异常的语言更新为:

According to this EF Core GitHub ticket you have to update the child/nested/owned type properties directly for it to track properly. This was supposed to be fixed in EF 2.1 (currently only available as a release candidate) but may have not made the cut. In 2.0.3 they updated the verbiage of the exception to:

InvalidOperationException:无法跟踪实体类型"Parent.Child#Child"的实例,因为已经跟踪了另一个具有相同"{'ParentID'}"键值的实例. 在替换拥有的实体时,请在不更改实例的情况下修改属性,或者先分离先前的拥有的实体条目.

InvalidOperationException: The instance of entity type 'Parent.Child#Child' cannot be tracked because another instance with the same key value for {'ParentID'} is already being tracked. When replacing owned entities modify the properties without changing the instance or detach the previous owned entity entry first.

如果您使用的是DDD,则此消息的第二部分将使您感到不舒服.它告诉您,必须直接更新EF的子属性/嵌套属性的属性,以正确跟踪更改(这将DDD值对象破坏为不可变的).根据GitHub线程上的评论,此处为建议的DDD友好的解决方法,适合您的代码:

The second part of this message will make you throw up a little if you are using DDD. It is telling you that you must update the properties of the child/nested properties directly for EF to properly track the changes (which breaks DDD Value Objects as being immutable). As per a comment on the GitHub thread here is a suggested, somewhat DDD friendly, workaround adapted to match your code:

public void SetAddress(Address address)
{
    Guard.AssertArgumentNotNull(address, nameof(address));    
    Address.UpdateFrom(address);
}
// And on Address:
internal void UpdateFrom(Address other)
{
    Street = other.Street;
    // ...
}

-OR-

第二个建议的解决方法是通过分离实体,更新Address的实例,然后重新附加它来完成.我在实现过程中没有太多运气,但是将其发布以供后代使用.也许您会比我拥有更好的运气.

The second suggested workaround is done by detaching the entity, updated the instance of Address, then re-attaching it. I didn't have much luck with this workaround in my implementation, but will post it for posterity. Maybe you'll have better luck with it than I did.

context.Entry(employee.Address).State = EntityState.Detached;
employee.SetAddress(newAddress);
context.Entry(employee.Address).State = EntityState.Modified;

更新

我终于找到了EF Core团队的公开票证,可以跟踪该问题. 票证#10551 特别指出了该问题,并且仍在讨论中.它绝对不是EF Core 2.1的一部分,并且似乎已被放置在Backlog Milestone 3.0中.请注意,您可以对此问题进行投票,以使EF Core团队对此予以更多关注.

I finally found the open ticket with the EF Core team that can be tracked for this issue. Ticket #10551 specifically states the issue at hand and is still open. It definitely didn't make it to EF Core 2.1 and appears to have been placed in the Backlog Milestone 3.0. Note you can up-vote this issue as a way to get the EF Core team to put more attention on it.

更新2 EF Core 2.2引入了Tracked Graph组件,使这一过程更加流畅.但是,这确实要求您所有的EF实体都使用数据库生成的ID.此方法检查是否设置了实体密钥,然后将实体标记为已修改或已添加.可以将其扩展为包括删除,但是出于我的目的,我不希望这种行为.

UPDATE 2 EF Core 2.2 introduced a Tracked Graph component that makes this much more fluid. This does, however, require that all of your EF Entities use database generated ids. This method inspects whether the entity key is set, then flags the entity as modified or added. This can be expanded to include deletes, but for my purposes I don't want that sort of behavior.

internal void Upsert(object entity)
{
    ChangeTracker.TrackGraph(entity, e =>
    {
        if (e.Entry.IsKeySet)
        {
            e.Entry.State = EntityState.Modified;
        }
        else
        {
            e.Entry.State = EntityState.Added;
        }
    });

    #if DEBUG
    foreach (var entry in ChangeTracker.Entries())
    {
        Debug.WriteLine($"Entity: {entry.Entity.GetType().Name} State: {entry.State.ToString()}");
    }
    #endif
}

然后,在context.SaveChanges();之前使用context.Upsert(<YOUR ENTITY OBJECT>);.

这篇关于实体框架核心:无法使用嵌套值对象更新实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆