NHibernate recreates every associated item - c#

I have a very simple object models.
public class Contact
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Device Device { get; set; }
public virtual IList<string> Numbers { get; set; }
public Contact()
{
Numbers = new System.Collections.Generic.List<string>(3);
}
}
As you can see, the class Contact has an association with Numbers, which is a list of strings.
Here's the mapping:
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device, "DeviceId");
Table("Contacts");
HasMany(x => x.Numbers)
.Table("ContactNumbers")
.Element("Number")
.KeyColumn("ContactId")
.LazyLoad()
.Cascade.All()
.Not
.Inverse();
Note that I can't and don't want the collection to be inverse=true, because it's just a collection of string. This means that Contact is responsible for updating Numbers entries.
Now my problem is that, whenever I try to add a new number to an existing Contact, it deletes all associated numbers and recreates them individually. Isn't NHibernate smart enough to detect changes and update only changed items?
I think there should be a simple solution for my problem but don't know what.
Any help would be appreciated.

This is actually documented in NHibernate's documentation.
Bags are the worst case. Since a bag permits duplicate element values
and has no index column, no primary key may be defined. NHibernate has
no way of distinguishing between duplicate rows. NHibernate resolves
this problem by completely removing (in a single DELETE) and
recreating the collection whenever it changes. This might be very
inefficient.
Try using an <idbag> mapping instead, and create a surrogate primary key for that table. Unfortunately, looks like <idbag> is not yet supported in FluentNHibernate.
Also, take a look at other collection mapping options.

Related

Fluent NHibernate: Many to Many mapping on a single table

I have a Table of Companies, and I'm trying to build a matrix of competitors between them.
For example: McDonalds is a Competitor of Wendy's, and vice-versa.
Here's the mappings I've tried:
HasManyToMany(x => x.Competitors)
.ParentKeyColumn("IssuerID")
.ChildKeyColumn("CompetitorID")
.Table("Competitors")
.Cascade.All().Not.LazyLoad();
as well as:
Table("Issuer");
Id(x => x.Key).Column("Id").GeneratedBy.Assigned();
Map(x => x.Name).Length(1000);
HasManyToMany(x => x.Competitors).ParentKeyColumn("IssuerID").ChildKeyColumn("CompetitorID").Table("Competitors").Cascade.All().Not.LazyLoad();
When I add a competitor to an Issuer, I can see the correct mapping in the database.
So If I do:
McDonalds.AddCompetitor(Wendys);
I will see the correct data in the DB. I will also see the correct DB in the Entities when i get McDonalds using NHibernate.
But, if I return Wendy's from Nhibernate and look at the Competitors object:
Wendys.Competitors
I don't see McDonalds. I can understand why because Wendy's was added to McDonalds as a Child.
How can I modify this so I can view the competitors from both directions?
I came up with the following solution in my local tests. Actually I am not really satisfied as I would hope for a better support. Maybe there is. If someone finds it please post.
Having a store like this (shrinked down to the relevant parts of competitors):
public class Store
{
public virtual int Id { get; protected set; }
public virtual IList<Store> Competitors { get; set; }
}
I used the following mapping (same as you):
HasManyToMany(x => x.Competitors)
.ParentKeyColumn("Store_Id")
.ChildKeyColumn("Competitor_Id")
.Table("CompetitorsMapping");
As you state it does not work with this. But from the examples provided in the Git Repo of fluentnhibernate it wraps the add of the lists anyway.
The "FirstProject" example does something similar by adding an employee to a store. It first sets the store on the employee and then adds the employee to the Staff of the store (okay a bit different then). But it automatically takes care of dependencies.
So you could solve your problem by not setting directly but by wrapping the add and remove (From your sample code it looks like you already do that):
public class Store
{
public virtual void AddCompetitor(Store competitor)
{
if (!Competitors.Any(x => x.Id == competitor.Id))
{
competitor.Competitors.Add(this);
Competitors.Add(competitor);
}
}
public virtual void RemoveCompetitor(Store competitor)
{
competitor.Competitors.Remove(this);
Competitors.Remove(competitor);
}
}
What I especially don't like is the contains check on Competitors. Don't know how it performs and behaves. But without you can add the same competitor over and over. Making the list long.
I am new to fluentnhibernate. Maybe some setting could prevent the double adds automatically. I'd be interested in that setting.

NHibernate delete child from parent collection with delete cascade

I have two simple entities named Country and City.
public class Country : Entity
{
public Country()
{
Cities = new List<City>();
}
public virtual string Name { get; set; }
public virtual IList<City> Cities { get; set; }
}
public class City : Entity
{
public virtual Country Country { get; set; }
public virtual string Name { get; set; }
}
The DB used is SQL Server and City has a foreign key to Country with cascade delete.
I am using Fluent NHibernate, this is the mapping configuration for the relation:
public CountryMap()
{
Id(x => x.Id, "IdCountry").GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable().Length(50);
HasMany(x => x.Cities).KeyColumn("IdCountry").ForeignKeyConstraintName("FK_Cities_Countries")
.Not.KeyNullable().Cascade.AllDeleteOrphan().ExtraLazyLoad();
Table("Countries");
}
public CityMap()
{
Id(x => x.Id, "IdCity").GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable().Length(50);
References(x => x.Country, "IdCountry").ForeignKey("FK_Cities_Countries")
.Not.Nullable().Not.Insert().Not.Update().Cascade.All().LazyLoad();
Table("Cities");
}
All works fine, but after delete a country, cities remain in parent collection and I want the cities to be removed from that collection. (As EF does)
The only way I found to get it working is refreshing the session (Clear, Evict...)
Manual deletion of the collection items is not a solution. It in facts breaks the cascading feature.
In case we do have mapping Cascade.AllDeleteOrphan(), we should expect that deletion of Parent will deleted Children as well - no need to do more then delete Parent. Just ... NHibernate does not care about clearing that collection in memory (app server/application)
In case you are searching for long, stable, solid solution I would strongly suggest:
Split READ and WRITE operations
The current frameworks we have (like Web API) are helping us to go this direction. We should create set of operations:
PUT, POST, DELETE ... to handle client requests for data amendments
GET ... to retrieve data by ID or by Criteria (Find())
Each of these operations should have its own session, its own transaction, and should represent the unit of work. All or nothing. Either operation is successful and all is persisted, or not (rollback)
That will help our application being successful and growing in a longer/long run. We do a split. We care about
1) DELETE, UPDATE or INSERT (or more of them arround some root entity, could see more here).
2) we do READ operation - expecting only SELECT operations, therefore working with up-to-date data. Also check this, including comments
While this could be a bit out of scope, this cite form 9.8. Exception handling doc is also the clue:
...If the ISession throws an exception you should immediately rollback the transaction, call ISession.Close() and discard the ISession instance. Certain methods of ISession will not leave the session in a consistent state...
I wanted to demonstrate, that the operation (session, transaction) should have only one goal (WRITE, READ) and be as short as possible...
With that kind of mapping, the easiest way to work is to remove the City from the Country collection and save the Country. With the cascade, the city will be deleted and the collection will be in the state you want.

Fluent NHibernate Mapping: one-to-one (or none)

I have a following database scheme setup which I can't really change.
User
----
Id (primary key)
[Some simple properties...]
UserAdditionalData
------------------
Id (primary key)
[Some simple properties...]
USERID (foreign key to User)
It's clear that the User table doesn't really have any recollection whether or not it is linked to a UserAdditionalData record, so I don't think I can call this a true one-to-one mapping here since they also don't share a mutually exclusive PK.
However, in practice, I would like to be able to work on a User object and for example check if it has a UserAdditionalData record and if so, access its properties.
I've set up my BDO as such:
public class User
{
[Some simple properties...]
public virtual UserAdditionalData UserAdditionalData { get; set; }
}
public class UserAdditionalData
{
[Some simple properties...]
public virtual User User { get; set; } /* I have this here,
but I don't really ever
have to access it in this
direction */
}
I've set up my mapping as such:
public UserMapping()
{
Table("USER");
[Some simple properties...]
HasOne(x => x.UserAdditionalData).Cascade.None();
}
public UserExtraMapping()
{
Table("USER_ADDITIONAL_DATA");
[Some simple properties...]
References(x => x.User, "USERID").Unique();
}
This all compiles, but I see that my UserExtra object (when accessed via a User object) is always null.
I've tried a lot of different ways to go about it, read a lot on implementing this as a one-to-many. However, I'm still not being able to get it to work.
Any help would be much appreciated.
Thanks!
[Small UPDATE]: I only have to query the database, not save to it if that's relevant in any way.
Based on your small update, I would go with a simplified mapping. We would profit from NHibernate real mapping power, and also optimize the User loading. All that because we do need Read-Only mapping.
Firstly, we should introduce simple int property UserId on the Additional class
// extra class is having an int property containig the foreign key
public class UserAdditionalData
{
public virtual int UserId { get; set; }
}
// that would be the mapping:
public UserExtraMapping()
{
...
Map(x => x.UserId, "USERID");
}
Now, we will use well optimized mapping for lazy loading many-to-one (I.e. in comparison with one-to-one which loads both ends always, here we will get the reference data only if really needed!)
public UserMapping()
{
...
References(x => x.UserAdditionalData)
.LazyLoad()
.PropertyRef(e => e.UserId)
.Not.Insert()
.Not.Update()
;
}
So, for readonly I would do the best to use many-to-one mapping (References())
see also:
5.1.10. many-to-one
mapping by code, ManyToOne (scroll down to Fluent NHibernate's equivalent)

(Fluent) NHibernate table-per-hierarchy; Id is only unique along with discriminator

I have a legacy database mapping issue. The database is storing all of its lookup values (code/description) in one table, distinguished by a type code field. The tables referring to it do so with one column (the code, without the type code). The code table does not have a primary key constraint (!).
I have a class that looks like this:
public class Code
{
[StringLength(8)]
public virtual string CodeValue { get; set; }
[StringLength(2000)]
public virtual string Description { get; set; }
public virtual long? OrderBy { get; set; }
public virtual DateTime? StopDate { get; set; }
}
My initial mapping looked like this:
public class CodesMap : ClassMap<Code>
{
public CodesMap()
{
Table("CODES");
Id(x => x.CodeValue).Column("CODE_CODE").GeneratedBy.Assigned();
Map(x => x.Description).Column("DESCRIPTION");
Map(x => x.OrderBy).Column("ORDER_BY");
DiscriminateSubClassesOnColumn("TYPE_CODE", "INVALID")
.SqlType("VARCHAR2");
}
}
And then there are a bunch of sub-classes that differ only in their discriminator values.
Another mapping might reference this as:
...
References<FacilityType>(x => x.Type).Column("FACIL_TYPE_CODE").ReadOnly();
...
Now, this is all well and good, and everything works, since that reference knows the class, and therefore the discriminator value for the query, except...I only just hit the case where CODE_CODE is non-unique between two objects of different types (both subtypes of Code) in the same session. Oops.
CODE_CODE and TYPE_CODE are unique together, so the right thing ought to be to use them as a composite key. But then my References in the other class maps become impossible, because those tables only have a single column foreign key (obviously no FK constraint defined on table).
Short of adding a surrogate key on the code table, whatever shall I do?
In case, that we need to map look up values as readonly, solution would be surprisingly very easy. Instead of explicit inheritance, we will explicitly map each subclass. The discriminator will be moved to a WHERE clause:
public FacilityTypeMap()
{
Table("CODES");
// here we will use explicit runtime discriminator
// injected by NHibernate into each SELECT .. FROM clause for this type
Where(" TYPE_CODE = 'FACIL_TYPE' " )
Id(x => x.CodeValue).Column("CODE_CODE").GeneratedBy.Assigned();
Map(x => x.Description).Column("DESCRIPTION");
Map(x => x.OrderBy).Column("ORDER_BY");
// no more inheritance
// DiscriminateSubClassesOnColumn("TYPE_CODE", "INVALID")
// .SqlType("VARCHAR2");
}
This is very well working for SELECT. We just have to repeat that mapping for each Discriminator == each derived type.
see 5.1.3. class:
<class
name="ClassName" (1)
table="tableName" (2)
...
where="arbitrary sql where condition" (11)
(11) where (optional) specify an arbitrary SQL WHERE condition to be used when retrieving objects of this class
In case, we need to use this class also for insert, we have to do few more steps. Explicitly map column 'TYPE_CODE' as e.g. Discriminator, and set it in constructor to correct value (e.g. 'FACIL_TYPE'). It could be protected property mapped as .Not.Update()
string _discriminator = "FACIL_TYPE";
public virtual string Discriminator { get { return _discriminator; } protected set {} }
...
// mapping
Map(x => x.Discriminator, "TYPE_CODE").Not.Update()

EF 6.0 Cannot retrieve navigation property (Collection) using a Bounded (focused) context

I have started breaking up my "uber" context into smaller focused ones. In a simple scenario, I have Student and Lectures POCOS and my EntityTypeConfiguration defines a many to many relationship between the two in a new table called StudentsAndLectures.
These tables are part of a relationship network of tables defined in my uber context. However, I'd like to manage students and their lectures in a more targeted fashion with a focused context.
My POCO classes below.
public class Student
{
public Student()
{
Lecture = new List<Lecture>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Lecture> Lectures { get; set; }
}
public class Lecture
{
public Lecture()
{
Students = new List<Student>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Finally, My entity type mappers.
public class StudentMapper : EntityTypeConfiguration<Student>
{
public StudentMapper()
{
HasKey(x => x.Id);
HasMany(x => x.Lectures)
.WithMany(x => x.Students)
.Map(m =>
{
m.MapLeftKey("LectureId");
m.MapRightKey("StudentId");
m.ToTable("StudentsAndLectures");
});
Property(x => x.Name);
}
}
public class LectureMapper : EntityTypeConfiguration<Lecture>
{
public LectureMapper()
{
HasKey(x => x.Id);
HasMany(x => x.Students)
.WithMany(x => x.Lectures)
.Map(m =>
{
m.MapLeftKey("LectureId");
m.MapRightKey("StudentId");
m.ToTable("StudentsAndLectures");
});
Property(x => x.Name);
}
}
Also, My Focused context contains DbSets for only the Students and Lectures.
My problem, If I query for a specific student like below, using my focused context, my Navigation property for .Lectures returns empty. However if I use the full(uber) context that created the db my navigation property gets lazy loaded or eager loaded as i wish. Anyone know why this could be?
using(FocusedStudentContext db = new FocusedStudentContext())
{
var student = db.Students.Include(s => s.Lectures)
.FirstOrDefault(s => s.StudentID == 1234);
// Inspecting student here for the Lectures navigation property
// collection has 0 elements.
}
After further testing and experimenting I found that if I included One particular (none others) additional DbSet that exists in my model and it's related ModelBuilder configurations then all works fine. The DbSet is for an entity, Registration, and it's one that has a navigation property to Student with a HasRequired (x => x.Student). Another twist is, if i leave the ModelBuilder configurations for the Registration entity, but remove the DbSet<Registration> from my focused context, then my navigation property for Lectures stops getting added again. (The collection has 0 elements).
My confusion, how can adding a DbSet to my Focused context affect the way my navigation properties get resolved for tables/entities described above? And how can I resolve this issue. Any help will be appreciated.
You only need one many-to-many mapping, not two. But even though you could have two mappings, they should be identical. In your case, they aren't. Both mappings have the same columns in MapLeftKey and MapRightKey, but they start at different ends. Only the LectureMapper is correct.
Apparenty, the StudentMapper takes precedence, which I think is determined by the order in which mappings are added to the configuration. The effect is that EF is looking for Lectures by the StudentId value in the junction table: very wrong. I can't really explain the effect of including the other mappings and entities that you describe. I just assume that under different circumstances makes EF takes the other mapping first.
But it's just too easy to get MapLeftKey and MapRightKey wrong. I try to keep them apart by picturing it:
Lecture HasMany Student
Left: LectureId Right: StudentId
The MSDN description isn't too helpful, e.g. MapLeftKey:
Configures the name of the column(s) for the left foreign key. The left foreign key points to the parent entity of the navigation property specified in the HasMany call
The navigation property specified in the HasMany call is Students, the parent (or owner) of the property is Lecture, which is identitfied by LectureId... I go for the visualization.
UPDATE I guess I resolved this but not really. I found that if I remove the explicit mapping on the Student and Lectures many to many table and let EF do it that things work fine now.
HasMany(x => x.Students).WithMany(x => x.Lectures);

Categories

Resources