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

StackOverflow https://stackoverflow.com/questions/23553830

Вопрос

I have been coding C# for a good while now, and I generally use Entity Framework and implement the repository pattern. 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:

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. 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. With a complex and deeper nested model, this quickly becomes cumbersome.
  2. Fetch DbContext directly to prepare the pet for modification or adding. However, I implemented a repository to NOT access DbContext directly.
  3. Modify PersonRepository.Update(Person person) to automatically update the state of underlying pets. 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. 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. 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 с атрибуция
Не связан с StackOverflow
scroll top