NHibernate additional UPDATE query run - c#

I have a many-to-many relationship defined between two entities using an association table and cascade="save-update".
Entity1 contains a list of Entity2, and conversely, Entity2 contains a list of Entity1. The SQL outputted from this worflow seems ok...
Create an Entity1 and Entity2 object
Add Entity2 to the List on Entity1
Call session.Save on Entity1
-> Insert statements are run for both entities and then a record inserted into the association table linking them together.
However, if I first call session.Save on Entity2, add it to the List, then call session.Save on Entity1 there is an additional UPDATE statement run which sets all of Entity2's values to exactly the same as what was inserted at the start.
Although not causing any issues, it is an additional query reduce performance. I've played with the inverse attribute but this doesn't eliminate the extra update statement. Currently both sides have inverse="false" as I want the association table updated no matter which entity is saved.
Any ideas?

From the docs:
inverse (optional - defaults to false): If enabled, Hibernate will not try to insert or update the properties defined by this join.
My suggestion: In some cases to prevent additional updates with 2-way joins BOTH collections may need to be updated:
Contact c;
Address a;
c.Addresses.Add(a);
a.Contacts.Add(c);
You are probably getting the additional Update statement because both sides have inverse=false and the first entity's relationship list was saved empty. NH is only doing what it thinks it needs to in order to get all relationships updated.

Thanks for the response but I believe I've found the problem. I had a repository class for each type of entity which implemented the CRUD operations. Each CRUD operation started it's own session/transaction, called Save/Update/etc then closed the transaction/session.
If I call the session.Save for Entity2 then session.Save for Entity1 (which contains the list of Entity2) NH seems to know that it has already persisted Entity2 in the session and hence doesn't try to update the record.
On the other hand, if I call session.Save for Entity2 then session.Save for Entity1 in a separate session/transaction it wants to update Entity2 again. I'm new to NH so not sure how it tracks which objects require updating, but it must reset between sessions?
Kinda makes my nice DDD repositories a little less useful though! Maybe the repositories should use a singleton session or something similar to avoid this problem?
Thanks,
John

Related

How to manually load related entities in a N:N relationship?

I am using EF5 and when the the relationship is 1:N, if I want to load related entities I do the following:
With T-SQL I load from database the main entities with a T-SQL like that:
select *
from MainEntities
where ...
with T-SQL I load the related entities
select *
from RelatedEntities
where IDMainEntity IN (---)
At this point EF populate the property navigation of the main entities with the related entities. Also, in the local property of the type of each entity in the dbContext I have all the entities of each type.
However, if i do the same with a N:N relationship, I don't have the entity of the middle table of the relation, and when I execute the queries I have in the local of the dbContext the entities of each type, but the property navigation is not populated.
I would like to know why and if it exists some alternative.
I use this way because I want to use T-SQL for create dynamic queries. If I use eager loading I don't have the same flexibility to dynamic queries than when I use TSQL, and it is less efficient. If I use explicit loading I to do N additional queries, one of each record in the results of the main entity With my way, I only one additional query, because I get all the related entities at once. If I use lazy loading I have the same problem, N additional queries.
Why EF does not populate the related properties when the relation is N:N?
Thanks.
The feature you are talking about is called Relationship Span or Relationship Fixup and indeed - as you have noticed - it does not work for many-to-many relationships. It only works if at least one end of the association has multiplicity 1 (or 0..1), i.e. it works for one-to-many or one-to-one relationships.
Relationship Span relies on an entity having a foreign key. It doesn't matter if it has an explicit foreign key property (foreign key association) or only a foreign key column in the corresponding database table without a property in the model (independent association). In both cases the FK value will be loaded into the context when the entity gets loaded. Based on this foreign key value EF is able to figure out if a related entity that has the same primary key value as this FK value is attached to the context and if yes, it can "fixup the relationship", i.e. it can populate the navigation properties correctly.
Now, in a many-to-many relationship both related entities don't have a foreign key. The foreign keys are stored in the link table for this relationship and - as you know - the link table does not have a corresponding model entity. As a result the foreign keys will never be loaded and therefore the context is unable to determine which attached entities are related and cannot fixup the many-to-many relationship and populate the navigation collections.
The only LINQ queries where EF will support you to build the correct object graph with populated navigation collections in a many-to-many relationship are eager loading...
var user = context.Users.Include(u => u.Roles).First();
...or lazy loading...
var user = context.Users.First();
var rolesCount = user.Roles.Count();
// Calling Count() or any other method on the Roles collection will fill
// user.Roles via lazy loading (if lazy loading is enabled of course)
...or explicit loading with direct assignment of the result to the navigation collection:
var user = context.Users.First();
user.Roles = context.Entry(user).Collection(u => u.Roles).Query().ToList();
All other ways to load the related entities - like projections, direct SQL statements or even explicit loading without assignment to the navigation collection, i.e. using .Load() instead of .Query().ToList() in the last code snippet above - won't fixup the relationship and will leave the navigation collections empty.
If you intend to perform mainly SQL queries rather than LINQ queries the only option I can see is that you write your own relationship management. You would have to query the link table in addition to the tables for the two related entities. You'll probably need a helper type (that is not an entity) and collection that holds the two FK column values of the link table and probably a helper routine that fills the navigation collections by inspecting the primary key values of the entities you find as attached in the DbSet<T>.Local collections and the FK values in the helper collection.
To add on #Slauma answer:
I faced the same problem recently, getting frustrated that the navigation property is not being set after calling Query().Where().Load(), although I can see that the objects are loaded into the DbContext.
I needed the collection to be part of my main object and use it as you would any other navigation property and not just manage a separate collection, so I did this:
project.Labels = this.Context
.Entry (project)
.Collection (p => p.Labels)
.Query ()
.Where (l => l.CreateUserName == this.UserId)
.ToList();
The problem with this is that EF thinks I added new relationships, which I can't blame it, but it is not what I wanted. As a result, when trying to save the Project object I got an exception when EF tried to insert the relationship into the link table because a row with the same key (projectId + labelId) already exists.
So, the final was to reset the state of the relationships between the project and the labels:
foreach (Label l in project.Labels)
{
((System.Data.Entity.Infrastructure.IObjectContextAdapter)this.Context.AsDbContext ()).ObjectContext.ObjectStateManager.ChangeRelationshipState<Project> (project, l, p => p.Labels, EntityState.Unchanged);
}
After that I was able to use the Labels property just like any other navigation property, not caring that behind the scenes it's a many-to-many relationship.

How to update the cross reference table?

In EF cross reference tables are abstracted away by creating many-to-many relationships. E.G.
There's a SQL table dbo.TrialContactCrossReference that relates TrialContactId to TrialID. Now, EF did not generate an Entity TrialContactCrossReference because it went with this MANY-MANY relationship thing. How do I add a new row to said table?
I tried
context.TrialContacts.??? and context.ClinicalTrials.??? and just don't know what to do with this. If I have a new Contact that I want to relate to a trial how am I supposed to go about it?
trial.Contacts.Add(contact);
OR
contact.Trials.Add(trial);
OR (and my advise)
you could create an additional entity for cross reference table. this will convert many-many to 2 one-many relationships. more then %90 cases crosstables has additional columns (at least IsActive, RecordDate etc.) even it doesnt, it may be so in future and it requires you make lots of changes in code.
If I have a new Contact that I want to relate to a trial how am I supposed to go about it?
Assuming you have an existing Contact instance just do:
trial.TrialContacts.Add(contact);
context.SaveChanges();
EF will take care of the intermediate table insert for you. Note that adding Contacts and Trials works the same as if they weren't related.
There's a trick that was not obvious to me when setting this up. TableA must be added to TableB, not just to itself. In fact, looking at the generated entities each entity has a List<> of the other entity.
class TableA
{
List<TableB> TableB;
}
class TableB
{
List<TableA> TableA;
}
For example, if I want to add a TrialContact to a ClinicalTrial then I write:
context.ClinicalTrials.TrialContacts.Add(trialContact);
context.SaveChanges()
Then the xRef table be updated to reflect the relationship.

EF DB-first mapping mess

I have a frustrating situation owing to this little quirk of EF. Here's a simple demo of the behavior. First the DB schema:
As you see, RestrictedProduct is a special case of product, which I'm intending to make a subclass of Product with some special code.
Now I import to an EF data model:
Oops! EF saw that RestrictedProduct had only 2 fields, both FKs, so it mapped it as a one-to-many relationship between Product and Restriction. So I go back to the database and add a Dummy field to RestrictedProduct, and now my EF model looks much better:
But that Dummy field is silly and pointless. Maybe I could delete it? I blow away the field from the DB table and the entity model, then refresh the model from the DB...
Oh, no! The Product-Restriction association is back, under a new name (RestrictedProduct1)! Plus, it won't compile:
Error 3034: Problem in mapping fragments starting at lines (x, y) :Two entities with possibly different keys are mapped to the same row. Ensure these two mapping fragments map both ends of the AssociationSet to the corresponding columns.
Is there any way to prevent this behavior, short of keeping the Dummy field on the RestrictedProduct table?
I just came across the same issue, and as an alternative to putting the dummy field in your RestrictedProduct table to force the creation of an entity you can also make your RestrictedProduct.RestrictionId field nullable and EF will then generate an entity for it. You can then modify it to use inheritance and any subsequent "Update model from database" will not cause undesired nav properties. Not really a nice solution but a work around.
Let's walk slowly into your problem.
1st thing you need to decide is if the restricted product is
really a special case of product or is it a possible extension
to each product.
From your original DB Scheme it seems that any product may have
a relation to a single restriction however a single restriction
can be shared among many products.. so this is a simple 1 to many
situation which means that restricted product is NOT a special case
of product! Restriction is an independent entity which has nothing
to do with product in a specific way.
Therefore EF is correct in the 1st importation of your scheme:
1. a product can have 0 or 1 restrictions.
2. a restriction is another entity which can be related to many products.
I do not see your problem.

NHibernate - Should all child objects be updated when parent is saved, when cascade is save-update?

I have a bidirectional one to many relationship defined with cascade="all" defined on both ends in the mapping and inverse="true" on the one-to-many end.
When I call SaveOrUpdate a new entity the it correctly inserts a row into the parent table, then inserts all the child objects in the child table.
However, if I have an existing entity and update some properties of the child objects (say alter some string properties) then call SaveOrUpdate on the parent entity, it only updates the information in the parent table. I was expecting it to update all the child entities also.
Is this the expected behaviour? Do I need to manually update all the child objects myself? I'm not sure if I've messed something up in my mappings (in which case I'll add them to the question) or if this is how NHibernate is supposed to behave.
Edit: found the error; problem exists between keyboard and chair as usual.
Never mind, I was just being stupid and updating a property that isn't mapped to any columns.
Looks like NH will update child records that need updating only.

navigation property to soft-deleted entity

I have 2 entites:
Im my DB they look like:
Vehicles(Id, VehicleNumber, IsDeleted, WorkerId)
Workers(Id, Name, Address)
And in my edmx:
VehicleId: Id, VehicleNumber, IsDeleted, WorkerId, Worker
Workers: Id, Name, Address, VehiclesList
As you can see, Vehicles table contains soft deleted rows. Now when I get Worker with id=2, I got all his vehicles, including the one I soft deleted. How can I retrieve only the undeleted vehicles?
Badly. EF has very limited support for soft deletes. Actually the only possibility is using conditional mapping where you explicitly hardcode (it cannot be changed at runtime) to your mapping condition saying that you don't want to load entities having IsDeleted = 0. Check mapping details:
But it has very bad consequences:
IsDeleted column cannot be mapped - it already defines mapping internally
Your model can never be used to load soft deleted entities even if you want
The first problem can be solved by mapping stored procedure to delete operation for the Vehicle entity and the second problem can be solved by separate model for auditing and retrieving deleted entities.
Also conditional mapping is not supported by code first - it requires EDMX file.
is lazyloading enabled? then try to limit result set with a where:
worker.VehiclesList.Where(x=>!x.IsDeleted)
also you can put a condition to a vehicles table mapping in model desiner isdeleted = false. Soft deleted vehicles will not be retrived at all

Categories

Resources