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

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

Domanda

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?

È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top