3

I have a three layer application. The layers are DAL, BL and UI. I'm using automapper to transform my entities taken from database context to up level layers. As far as I can see, the mapped entities are failed to be tracked by EF any longer.

Anyway the problem which I face occurs at the moment of updating the entities existing in DB, to be more exact the updating EF entities by DTO entities. The DTO from upper layer is mapped back to entity to complete update operation. However mapped from DTO entity and all its navigation properties and collections don't exist in context because they are not being tracked.

EF accepts this entity and its entities graph as a new information without taking into consideration the fact that this entity and some entities that included in it can already exist in context.

Here some example.

I have two models, Student and Standard.

public class Student
{
    [Key]
    public Guid StudentID { get; set; }
    public string StudentName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public decimal Height { get; set; }
    public float Weight { get; set; }

    public Standard Standard { get; set; }
}

public class Standard
{
    [Key]
    public Guid StandardId { get; set; }
    public string StandardName { get; set; }

    public ICollection<Student> Students { get; set; }
}

My context:

public class SchoolContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set;}
}

I also have a StudentMap and StandardMap that look the same as the models mentioned above.

Automapper profile:

public class AutomapperProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Student, StudentMap>();
        Mapper.CreateMap<StudentMap, Student>();
        Mapper.CreateMap<Standard, StandardMap>();
        Mapper.CreateMap<StandardMap, Standard>();
    }
}

And here the code I run:

 Mapper.AddProfile(new AutomapperProfile());

 var student1 = new Student
                          {
                              DateOfBirth = DateTime.Now,
                              Height = 195,
                              StudentID = Guid.NewGuid(),
                              StudentName = "Bob",
                              Weight = 144
                          };

var student2 = new Student
                           {
                               DateOfBirth = DateTime.UtcNow,
                               Height = 170,
                               StudentID = Guid.NewGuid(),
                               StudentName = "John",
                               Weight = 95,
                           };

var standard = new Standard
                           {
                               StandardId = Guid.NewGuid(),
                               StandardName = "New Standard",
                               Students = new List<Student> { student1 }
                           };

using (var schoolContext = new SchoolContext())
{
    schoolContext.Standards.Add(standard);
    schoolContext.Students.Add(student2);

    schoolContext.SaveChanges();

    var standardInContext = schoolContext.Standards.First();
    var studentInContext = schoolContext.Students.First(student => student.StudentID == student2.StudentID);

    var mappedStandad = Mapper.Map<Standard, StandardMap>(standardInContext);
    var mappedStudent = Mapper.Map<Student, StudentMap>(studentInContext);
    mappedStandad.Students.Add(mappedStudent);

    var standardEf = Mapper.Map<StandardMap, Standard>(mappedStandad);
    //On attach attempt exception will be thrown
    //because standard with the same Id already exist in context
    schoolContext.Set<Standard>().Attach(standardEf);
    schoolContext.Entry(standardEf).State = EntityState.Modified;

    // with this solution two additional student will be added to the context, 
    // exception will be thrown on SaveChanges, because students with 
    // same ID already exist
    // var standardEf = Mapper.Map(mappedStandad, standardInContext);
    // schoolContext.Set<Standard>().Attach(standardEf);
    // schoolContext.Entry(standardEf).State = EntityState.Modified;
    schoolContext.SaveChanges();
}

I'll be thankful for any help! I've examined whatever I could but in vain.

2 Answers 2

4

The line schoolContext.Set<Standard>().Attach(standardEf); gives exception because you already added Standard entity with the same StandardID earlier schoolContext.Standards.Add(standard);.

The solution:

  1. Request Standard entity from the context by StandardMap.StandardID

  2. Map property values from StandardMap transfer object to the requested Standard entity (manually or use Mapper.Map<Source, Destination>(source, destination);)

  3. Save changed

ADDED

If you want to use AutoMapper you have to properly configure mapper to request nested entities from context instead of creating them, like:

Mapper.CreateMap<StudendMap, Student>.
    ForMember(
        x => x.Standard,
        m => m.ResolveUsing(
            s => Context.Set<Standard>.Find(s.StandardID)))
Sign up to request clarification or add additional context in comments.

4 Comments

well, in your case the problem occurs at a time when you update entity in context with the method Mapper.Map<Source, Destination>(source, destination). Because all object that included in source object(DTO) are not tracked by EF, it will add them to the context(in fact some of that objects already exist in context). So it will give you exception because of keys duplicates while saving to database
Thank you, looks like solution of my issue. But it becomes routine when my entity contains a lot of references to the other objects
Indeed @DmitryKoshevoy which is the exact reason we are using AutoMapper in the first place. I've still not found a nice solution to this.
How do I avoid The operation cannot be completed because the DbContext has been disposed errors using EF with this approach?
0

Attaching a full graph is a complex task. In this case EF is attaching Student instances as Added, this is the behavior when attaching an object with a collection.

To avoid getting new students you should first attach all standard's students to the context so they start being tracked (as unmodified) then attach the standardEf entity.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.