3

I'm using EF code first with the following model:

public class Root
{
    public ChildA A { get; set; }
    public ChildB B { get; set; }
    public ChildC C { get; set; }
}

Suppose you have a controller

public class RecordController 
{
    ...

    public void Save(Root root)
    {
        ...
    }

    ...
}

and your Root controller has received a model from client that contains the following changes: property A is totally new it has not yet been added to database and needs to be created, property B already exists in database and needs to be updated, property C not changed.

Action Save is not aware of what the property changes are, it just needs to update the Record properly and create missing or update existing sub models, it is also possible that some Child classes may also have their own nested changes, so I need a method that will somehow recurse through the model compare new model to existing one and will apply appropriate changes. So how do I do that?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Lu4
  • 14,873
  • 15
  • 79
  • 132
  • 1
    +1 for the method name. Submitting an object graph is (still) a tedious job in EF. In Lerman & Miller's book _DbContext_ there is a proposed approach with manually coded self-tracking entities (of sorts). Not the silver bullet yet imo. – Gert Arnold Jun 22 '13 at 20:42
  • 1
    It can be useful: http://stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record – Wojciech Kulik Jun 22 '13 at 22:06

1 Answers1

0

I've ended up with concatenating each of my model and ViewModel classes with EntityState property, so now when I change some property I set the EntityState to changed state, when I create one I set the property to state Added, initially models are initialised with Unchanged state, basically it looks something like the following:

[Table("City")]
[KnownType(typeof(Country))]
public class City
{
    public City()
    {
        Airports = new List<Airport>();
        LastUpdate = DateTime.Now;
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int32 Id { get; set; }

    public Int32? CountryId { get; set; }

    [StringLength(50)]
    public String Name { get; set; }

    [Range(-12, 13)]
    public Int32? TimeZone { get; set; }

    public Boolean? SummerTime { get; set; }
    public DateTime? LastUpdate { get; set; }

    [ForeignKey("CountryId")]
    public virtual Country Country { get; set; }

    [NotMapped]
    public EntityState? EntityState { get; set; } // <----------- Here it is
}

Then on server I do the following

    [HttpPost, HttpGet]
    public HttpResponseMessage SaveRecord(RecordViewModel record)
    {
        var model = Mapper.Map<Record>(record);

        if (!ModelState.IsValid)
        {
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

        db.Attach(model);

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
        }

        return Request.CreateResponse(HttpStatusCode.OK);
    }

Here is implementation for Attach method:

    public void Attach(City entity)
    {
        if (entity != null)
        {
            Attach(entity.Country);

            AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
        }
    }
    public void Attach(Country entity)
    {
        if (entity != null)
        {
            AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
        }
    }

AttachAndMarkAs has the following implementation:

    public void AttachAndMarkAs<T>(T entity, EntityState state, Func<T, object> id) where T : class
    {
        var entry = Entry(entity);

        if (entry.State == EntityState.Detached)
        {
            var set = Set<T>();

            T attachedEntity = set.Find(id(entity));

            if (attachedEntity != null)
            {
                var attachedEntry = Entry(attachedEntity);

                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {
                entry.State = state;
            }
        }
    }
Lu4
  • 14,873
  • 15
  • 79
  • 132