user1675891
user1675891

Reputation:

Entity updates its columns but not the ones with foreign keys

I have an entity that I'm modifying and then saving. There are two properties and both are changed. I've verified that both are not null and set to what I intended. However, when I show it in the database, I can see that only the first one changes (the column Some) while the other stays unchanged (the column ThingId).

public class Holder
{
  public string Some { get; set; }
  public Thing Thing { get; set; }
}

public class Thing
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Holder> Holders { get; set; }
}

The storage is done in a method as follows.

model.Holders.Attach(holder);
model.Entry(holder).State = EntityState.Modified;
model.SaveChanges();

The relation between them is defined using Fluent API in the following way.

builder.Entity<Holder>()
  .HasOptional(_ => _.Thing)
  .WithMany(_ => _.Holders)
  .Map(_ => _.MapKey("ThingId"));

What on Earth am I missing? The issue doesn't come up when creating new holders. Only when trying to update.

Upvotes: 0

Views: 436

Answers (3)

Konrad Viltersten
Konrad Viltersten

Reputation: 39068

I'd suggest three possible approaches to resolve the issue.

First one is retracting some of the control from EF and managing the connection by yourself. Basically, you'd need to introduce explicit fields that carry the foreign key and explicitly see those being set.

The second one is allowing EF to manage the updates but to tell it very explicitly how to do it. In principle, you'd have to tell it to treat an entity as added, manipulated, deleted etc.

Lastly, and this is the one that I'd recommend because it's the right, object oriented approach to ORM paradigm, is to fix the issue of EF not recognizing the entity to begin with. In your classes, I can't see anything about the comparison method being implemented (there are those for equality, hash control etc.) and if you google those, you probably notice that there are a whole lot of goodies that EF relies on and which we usually are a bit sloppy with (and get away with it because the computer intuitively helps us).

Also, a disclaimer. I've hear a lot of times (and stated so myself) that in some cases, EF won't suffice and that one has to apply one of the two initial methods. In retrospective, it's always turned out to be something with the code elsewhere (be that forgotten Hash implementation, confused, non-unique IDs or simply a bug in migrations, not updating where it should update). I'm not saying that it's the case here nor everywhere. I'm just saying that I have no data suggesting otherwise, other than others' testimonials.

Upvotes: 0

bubi
bubi

Reputation: 6491

Working with detached objects is quite tricky.
Anyway, one way is to add a property for the foreign key and handle it (not just add the property). See other answer for details. If you want to work with detached entities I think that this is the best choice. It does not work if you need to work with n-m relationships (HasMany WithMany).

Another solution is this (This works also with n-m relationship)

context.Holders.Attach(holder);
context.Entry(holder).State = EntityState.Modified;


var manager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
manager.ChangeRelationshipState(holder, holder.Thing, "Thing", EntityState.Added);

context.SaveChanges();

The problem about this solution is that EF generates an update query similar to this

update [Holders]
set [Some] = @p0, [ThingId] = @p1
where (([Id] = @p2) and [ThingId] is null)

@p0 = "Holder updated"
@p1 = 100
@p2 = 200

(look at the where clause)

You have a similar behaviour if you change your update method to this (that is more readable).

var thing = holder.Thing;
holder.Thing = null;
var attachedHolder = context.Holders.Attach(holder);
attachedHolder.Thing = thing;
context.Entry(holder).Property("Some").IsModified = true;
context.SaveChanges();

Also in this case the generated update query is

update [Holders]
set [Some] = @p0, [ThingId] = @p1
where (([Id] = @p2) and [ThingId] is null)

@p0 = "Holder updated"
@p1 = 100
@p2 = 200

So, in both cases you need to know the original Thing (that can be detached). If you know it you can change your code in this way:

var thing = holder.Thing;
holder.Thing = myOldThing; // for example new Thing() {Id = 2} works
var attachedHolder = context.Holders.Attach(holder);
attachedHolder.Thing = thing;
context.Entry(holder).Property("Some").IsModified = true;
context.SaveChanges();

Actually I haven't read this part of EF source code but I that this behaviour think is related to the relationship handler that can manage also n-m relationships (HasMany-WithMany). In this case EF generates also the "support" table for the relationship and the primary key columns is the sum of primary key columns of the two tables. Updating this kind of relationships requires a where on the primary key of the support table.

Upvotes: 1

Jatin Nath Prusty
Jatin Nath Prusty

Reputation: 572

When you say

model.Entry(holder).State = EntityState.Modified;

EF understands that the scalars in the holder entity are modified.

So now you want to let EF know that "Thing" has also changed. So you would have to explicitly state it by saying:

model.Entry(holder.Thing).State = EntityState.Modified;

OR if you don't want to do this then you have to add the property for ThingID to your model and populate it likewise.

You can go through https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s03.html for understanding states in EF.

Upvotes: 1

Related Questions