11

我有一个具有值对象的实体,并且该值对象具有另一个值对象。我的问题是,当更新实体和值对象时,具有父值对象的实体得到更新,但子值对象没有。注意,我使用了最新版本的 Entity Framework Core 2.1.0-rc1-final

这是父实体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

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

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使用从用户界面获得的新值更改属性。

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

SetAddress方法:

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

2 回答 2

22

根据此 EF Core GitHub 票证,您必须直接更新子/嵌套/拥有类型属性才能正确跟踪。这应该在 EF 2.1 中得到修复(目前仅作为候选发布版提供),但可能没有成功。在 2.0.3 中,他们将异常的措辞更新为:

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

如果您使用的是 DDD,这条消息的第二部分会让您有点呕吐。它告诉您必须直接更新子/嵌套属性的属性,以便 EF 正确跟踪更改(这将 DDD 值对象破坏为不可变)。根据对 GitHub 线程的评论,这里是一个建议的、有点 DDD 友好的解决方法,适用于匹配您的代码:

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

-或者-

第二个建议的解决方法是分离实体,更新 的实例Address,然后重新附加它。在我的实现中,我对这种解决方法没有太多运气,但会为后代发布它。也许你会比我有更好的运气。

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 团队更加关注它。

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

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.Upsert(<YOUR ENTITY OBJECT>);before context.SaveChanges();

于 2018-05-23T12:18:39.360 回答
-1

一个更简单的替代方法可以在Owned type property not persisting for a modified entity in EF Core 中找到

简而言之,而不是

_context.Entry(contactModelFromRequestBody).State = EntityState.Modified;

你应该使用

_context.Update(contactModelFromRequestBody);
于 2020-03-09T23:52:44.533 回答