views:

39

answers:

1

I have a table of Persons and a table of Things, where each Thing is owned by a Person and each Person has a FavoriteThing.

Persons
   PersonID int <PK>
   FavoriteThingID int <FK>

Things
   ThingID int <PK>
   PersonID int <FK>

I would like to be able to add a Person and his/her favorite Thing, as well as setting that Thing's PersonID to the new Person, in a single transaction, without having DTC promote the transaction to distributed. Wrapping the operation in a TransactionScope() and manually managing the Entity connection does not appear to work:

        ThingEntities ent = new ThingEntities();

        using (TransactionScope scope = new TransactionScope())
        {
            ent.Connection.Open();

            Thing t = ent.CreateObject<Thing>();

            ent.Things.AddObject(t);

            ent.SaveChanges(false);

            Person p = ent.CreateObject<Person>();
            t.Person = p;
            ent.Persons.AddObject(p); 
            p.FavoriteThing = t;

            ent.SaveChanges(false);

            scope.Complete();

            ent.AcceptAllChanges();
            ent.Connection.Close();
        }

This results in a "Unable to determine a valid ordering for dependent operations." exception on the second SaveChanges() call.

Is there a simple way to do this?

Thanks

A: 

You don't need TransactionScope for this. You can do it in one call to SaveChanges, which means one transaction.

Person p = ent.CreateObject<Person>();
p.FavoriteThing = ent.CreateObject<Thing>();
ent.Persons.AddObject(p); 
ent.SaveChanges();
Craig Stuntz
Unless I am missing something, when you do it this way, Thing.PersonID does not get set. So, p.FavoriteThing points to the newly created Thing, but Thing.PersonID is null.
erg39
I see now the question was not entirely clear that I wanted Thing.PersonID to be set. Edited the question.
erg39
It will get set if you have a FK constraint on the column and a two-way association (and you should) with exactly the code I give. This is automatic.
Craig Stuntz
Hmm... This is my first foray into EF, perhaps I do not understand. I added back the non-null FK constraints in each table and regenerated the .edmx, which results two 1:* associations in the designer, one each direction. Is this what you mean by "two-way association", or is that something different that needs to be created manually? With the non-nullable FKs I get an "Entities in 'ThingEntities.Things' participate in the 'FK_Things_Persons' relationship. 0 related 'Person' were found. 1 'Person' is expected." exception from the code you gave. Thanks for your help.
erg39
Shouldn't p.FavoriteThing be 1:0..1? You can only have one favorite, right? Your error implies that there is a Person.Things property which you do not show in your question. Is that true? If so you must also do `p.Things.Add(p.FavoriteThing)` after assigning `FavoriteThing`.
Craig Stuntz
Ah yes, you are correct, p.FavoriteThing should be 1:0..1, thank you. Yes, there is a Person.Things navigation property in the Person entity. If I add `p.Things.Add(p.FavoriteThing)` I get the error I originally reported, **"Unable to determine a valid ordering for dependent operations."** If I make Thing.PersonID nullable, I can create the Person and its FavoriteThing, `SaveChanges()`, and then add `Person.FavoriteThing` to `Person.Things` and `SaveChanges()` again, but I want to wrap them in one operation if possible.
erg39
OK, I see what's going on here. I don't have this problem because we use GUID PKs. With an identity column you need to get the PK value from the server. Normally that's no problem, but you have a circular reference. So you need an INSERT and then an UPDATE, and the EF won't generate that automatically. So your choices are: 1) Use GUID PKs, and assign in app. 2) Make TransactionScope work. EF 4 may stop the escalation? 3) Change your schema to remove the circular reference. 4) Insert with a stored proc.
Craig Stuntz
Ok, so it appears that with this schema, you cannot really do it. I have considered (1) and have some possible alternatives for (3). I'd rather not use a sproc, and this is already with EF 4, though I do not have a deep understanding of what the rules are that dictate the escalation. Thanks for you time.
erg39