Artem A
Artem A

Reputation: 2438

EF doesn't updated related entities, only simple fields

I use codeFirst and independent assosiations in EF 6.1 EF doesn't wont to update detached entities. Attach and StateModified doesn't help for Related(FK) entities. Adding new Entities and upadting non related properties works correctly. Where is the problem?

I have main entity CoachTransaction :

public class CoachTransaction : BaseEntity
    {
        public DateTime? TransactionDate { get; set; }

        public int? Season { get; set; }
        public virtual CoachPosition Position { get; set; }
        public virtual Institution StartInstitution { get; set; }
        public virtual Institution EndInstitution { get; set; }       
    }

Mapping for it:

public MapCoachTransaction()
        {
            ToTable("CoachTransactions");
            HasKey(x => x.Id);

            Property(x => x.Id).HasColumnName("TransactionId");
            Property(x => x.TransactionDate);

            HasOptional(x => x.Position).WithMany().Map(x => x.MapKey("CoachPositionId"));
            HasOptional(x => x.StartInstitution).WithMany().Map(x => x.MapKey("StartInstitutionId"));
            HasOptional(x => x.EndInstitution).WithMany().Map(x => x.MapKey("EndInstitutionId"));
        }

Related Entities looks like this:

 public class Institution : BaseEntity
    {
        public virtual InstitutionType InstitutionType { get; set; }
        public string Name { get; set; }
        public string ImageUrl { get; set; }        
    }

Mapping for Related entities:

 public MapInstitution()
        {
            ToTable("Institution");
            HasKey(x => x.Id);
            Property(x => x.Id).HasColumnName("InstitutionId");
            HasOptional(x => x.InstitutionType).WithMany(x => x.Institutions).Map(x => x.MapKey("InstitutionTypeId"));
            Property(x => x.Name).HasColumnName("InstitutionName");
            Property(x => x.ImageUrl).HasColumnName("InstitutionImageUrl");
        }

The usage is like this:

                using (var context = new CoachDBContext())
                {
                    IGenericRepository<CoachTransaction> repository = new GenericRepository<CoachTransaction>(context);
                   coachTransaction.Seasion = 100; // this works
                    coachTransaction.EndInstitution = newInstitution; this doesnt'
                    repository.Update(coachTransaction); // coachTransaction comes from POST to Controller
                    repository.Save();
                }

and repository.Updarte method ofcourse uses Attach:

public virtual void Update(TEntity entityToUpdate)
        {
            EntitySet.Attach(entityToUpdate);
            dbContext.Entry(entityToUpdate).State = EntityState.Modified;
        }

With this settings the SQL request looks like this:

exec sp_executesql N'UPDATE [dbo].[CoachTransactions]
SET [TransactionDate] = @0, [Season] = @1
WHERE ([TransactionId] = @2)
',N'@0 datetime2(7),@1 int,@2 int',@0='2015-04-21 20:40:38.1840996',@1=100,@2=24

So, there is no my endInstitution entity to be updated.

But if I request this entity before save:

  using (var context = new CoachDBContext())
                    {
                        IGenericRepository<CoachTransaction> repository = new GenericRepository<CoachTransaction>(context);
                        var coachTransaction = repository.GetById(coachTransaction.Id) // this fixes problem, but not acceptable in this case
                        coachTransaction.Season = 100; 
                        coachTransaction.EndInstitution = newInstitution; // now this will work
                        repository.Update(coachTransaction); // coachTransaction comes from POST to Controller
                        repository.Save();
                    }

And SQL will contain Related keys like :

exec sp_executesql N'UPDATE [dbo].[CoachTransactions]
SET [TransactionDate] = @0, [Season] = @1, 

[EndInstitution] = @2

WHERE ([TransactionId] = @3)
',N'@0 datetime2(7),@1 int,@2 int, @3 int',@0='2015-04-21 20:40:38.1840996',@1=100,@2=2,@3=24

P.S.

Also, usage of Foreignkey assosiation approeach fixes this problem with mappting like this:

Property(x => x.PositionId).HasColumnName("CoachPositionId");
            Property(x => x.StartInstitutionId).HasColumnName("StartInstitutionId");
            Property(x => x.EndInstitutionId).HasColumnName("EndInstitutionId");
            HasRequired(x => x.Position).WithMany().HasForeignKey(x => x.PositionId);
            HasRequired(x => x.StartInstitution).WithMany().HasForeignKey(x => x.StartInstitutionId);
            HasRequired(x => x.EndInstitution).WithMany().HasForeignKey(x => x.EndInstitutionId);

But it causes another problem, when I have to set two properties instead of one when update Related entity, like:

updatingObject.EndInstitution = existingInstitution2; 
updatingObject.EndInstitutionId = existingInstitution2.Id;

insead of one call: updatingObject.EndInstitution = existingInstitution2;

Also it requires useless Id properties. This looks wrong from my point of view. Is there some solution?

Upvotes: 1

Views: 114

Answers (1)

Miguel
Miguel

Reputation: 3952

The Attach method don't attach sub-entities. In order to do this, you need to Attach all the dependents target that you want to update. You can use GraphDiff to update a full graph without attaching the entities. The usage is something like this: using (var con

text = new TestDbContext())  
{
    // Update the company and state that the company 'owns' the collection Contacts.
    context.UpdateGraph(company, map => map
        .OwnedCollection(p => p.Contacts, with => with
            .AssociatedCollection(p => p.AdvertisementOptions))
        .OwnedCollection(p => p.Addresses)
    );

    context.SaveChanges();
}

This is the a brief introduction to the framework.

In addition, I have been reviewing the source code of Entity Framework and found a way to actually update an entity if you know the Key property in another case you need to check the AddOrUpdate implementation:

public void Update(T item)
{
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

Hope this help!

Upvotes: 4

Related Questions