简体   繁体   English

使用存储库模式更新实体框架中的集合的导航属性

[英]Updating navigation properties that are collections in Entity Framework with Repository pattern

I have been coding C# for a good while now, and I generally use Entity Framework and implement the repository pattern. 我已经使用C#进行了一段时间的编码,并且通常使用Entity Framework并实现存储库模式。 The repository pattern tells us that we should generally only maintain and access repositories for our aggregate roots. 存储库模式告诉我们,通常,我们只应维护和访问聚合根的存储库。 Consider the following example, where Person is the root: 考虑以下示例,其中Person是根:

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Pet> Pets { get; set; }
}

public class Pet 
{
    public int ID { get; set; }
    public string Name { get; set; }
}

The above model would mean that we should generally access pets through the PersonRepository. 上面的模型意味着我们通常应该通过PersonRepository访问宠物。 However, if I want to modify or add a pet to a person, I have never found an elegant way to do this. 但是,如果我想给一个人修改宠物或添加宠物,我从来没有找到过一种优雅的方法。

In order to correctly identify what to update, I need to call 为了正确识别要更新的内容,我需要致电

DbContext.Entry(myPet).State = EntityState.Modified;

However, this messes with my repository pattern. 但是,这与我的存储库模式混淆。 As far as I can see, I have three options: 据我所知,我有三个选择:

  1. Create a PersonRepository.AttachPet(Pet pet) method. 创建一个PersonRepository.AttachPet(Pet pet)方法。 With a complex and deeper nested model, this quickly becomes cumbersome. 使用复杂且更深层的嵌套模型,这很快变得很麻烦。
  2. Fetch DbContext directly to prepare the pet for modification or adding. 直接获取DbContext以准备修改或添加宠物。 However, I implemented a repository to NOT access DbContext directly. 但是,我实现了一个存储库以不直接访问DbContext。
  3. Modify PersonRepository.Update(Person person) to automatically update the state of underlying pets. 修改PersonRepository.Update(Person person)以自动更新基础宠物的状态。 Not very elegant either, and possibly a large task. 也不是很优雅,可能是一项艰巨的任务。

What am I missing here? 我在这里想念什么? Is there a better approach? 有没有更好的方法?

Yes there is a better approach. 是的,有更好的方法。 For a new person, use: 对于新手,请使用:

_context.People.Add(myPerson); //myPerson can have Pets attached

This will traverse all sub objects and mark them as NEW. 这将遍历所有子对象并将它们标记为NEW。 When updating person, after calling the above code, you need to set which pet objects are modified/deleted. 更新人员时,调用上面的代码后,您需要设置要修改/删除哪些宠物对象。

I learned this in a Pluralsight course Entity Framework in the Enterprise . 在“企业中的Pluralsight”课程“ 实体框架”中了解了这一点。 In it, Julie adds an extra field to Pets. 在其中,朱莉向“宠物”添加了一个额外的字段。

public enum ObjectState
{
    Unchanged,
    Added,
    Deleted,
    Modified
}

public interface IObjectWithState
{
    [NotMapped]
    [JsonIgnore]
    ObjectState ObjectState { get; set; }
}

public class Pet : IObjectWithState
{
    public int ID { get; set; }
    public string Name { get; set; }
}

You might want this on all of your database entities. 您可能希望在所有数据库实体上使用它。

In your repository 在您的存储库中

public void InsertOrUpdateGraph(Person entity)
{
    _context.People.Add(entity);
    if (entity.ID != default(int)) _context.ApplyStateChanges();
}

Some extensions 一些扩展

public static class ContextExtension
{
    public static void ApplyStateChanges(this DbContext context)
    {
        foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        {
            IObjectWithState stateInfo = entry.Entity;
            entry.State = stateInfo.ObjectState.ConvertState();
        }
    }

    public static EntityState ConvertState(this ObjectState state)
    {
        switch (state)
        {
            case ObjectState.Modified:
                return EntityState.Modified;
            case ObjectState.Added:
                return EntityState.Added;
            case ObjectState.Deleted:
                return EntityState.Deleted;
            default:
                return EntityState.Unchanged;
        }
    }
}

Works every time. 每次都能工作。

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

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