NHibernate - how to delete an item from many-to-many relationship? - c#

I've following mapping for two tables having a Many-to-Many relationship between them. How do I delete an entry from the mapping table, which is 'ProjectUser' in my case?
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.ProjectName);
Map(x => x.Description);
References<User>(x => x.Owner);
HasManyToMany(x => x.Users)
.Cascade.SaveUpdate()
.Table("ProjectUser")
.Not.LazyLoad();
}
public UserMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.UserName);
HasManyToMany(x => x.Projects)
.Cascade.SaveUpdate()
.Inverse()
.Table("ProjectUser")
.Not.LazyLoad();
}
EDIT: changed Cascade to SaveUpdate as suggested in answers. Here is the code I use to commit data to SQLite database.
using (var trans = session.BeginTransaction())
{
var existingUsers = project.Users.ToList();
foreach (var item in existingUsers)
{
if (selectedUsers.Count(x => x.Id == item.Id) == 0)
project.Users.RemoveAt(project.Users.IndexOf(item));
}
session.SaveOrUpdate(project); // This fixed the issue
session.Flush();
foreach (var item in selectedUsers)
{
if (project.Users.Count(x => x.Id == item.Id) == 0)
{
project.AddUser(session.Get<User>(item.Id));
}
}
session.SaveOrUpdate(project);
session.Flush();
trans.Commit();
}
// Add user code in Project class
public virtual void AddUser(User userToAdd)
{
if (this.Users == null)
this.Users = new List<User>();
userToAdd.Projects.Add(this);
this.Users.Add(userToAdd);
}
Whenever I try to save/update I'm getting the following error:
a different object with the same identifier value was already associated with the session: 10, of entity: Models.Project
EDIT2: Should use session.SaveOrUpdate(project) and session.Flush() to avoid error stated above.

If you only want to remove the ProjectUser entry and not actually delete the entity on the other side you need to change from Cascade.All() to Cascade.SaveUpdate().
Currently if you removed a user from a project and saved the project it would delete the ProjectUser entry and the User object.

My guess would be to remove the appropriate User entity from the Project.Users collection and save that project. The cascade would then remove the entry in "ProjectUser".

If you want to remove link between a Project and an User (it means removing record from the join table ProjectUser) you should act from noniverse side. In your case it means you should remove user entity from the Project's Users collection:
project.Users.Remove(user);
In my opinion cascade in many-to-many assoc. should be set to SaveUpdate. You don't want to delete the project when the user is deleted.

Related

session.Save forces insert on id 0 relation objects - Fluent NHibernate

So yeah, I'm trying to insert a new entity on my database, which has some foreign keys pointing to auxiliar tables, which in this case shouldn't be updated.
It has 8 total foreign keys, which are all working but for one which has to be id = 0;
using (var session = FluentNHibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(p);
transaction.Commit();
return p;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
I guess there's no need to show the structure of the entity itself but for the map of the object:
References(x => x.Jerarquia).Column("TCODJERARQUIALIN").Cascade.All().Not.Nullable();
References(x => x.Conformidad).Column("TCODCONFORMIDADL").Cascade.All().Not.Nullable();
References(x => x.Tramitacion).Column("TCODESTADOTRAMIT").Cascade.All().Not.Nullable();
References(x => x.SistemaReferencia).Column("TCODSISTEMAREFER").Cascade.All().Not.Nullable();
References(x => x.Vigencia).Column("TCODVIGENCIALINE").Cascade.All().Not.Nullable();
References(x => x.CategoriaGeometrica).Column("TCODCATGEOMETRIC").Cascade.All().Not.Nullable();
References(x => x.Territorio1).Column("TCODIDTERRITORI1").Cascade.All().Not.Nullable();
References(x => x.Territorio2).Column("TCODIDTERRITORI2").Cascade.All().Not.Nullable();
Structure of the rest of the mappings comes to be like this, no matter which the related item is:
Id(x => x.TCODJERARQUIA).Column("TCODJERARQUIA").Not.Nullable();
Map(x => x.TTEXDESCJERAR_ES).Column("TTEXDESCJERAR_ES").Not.Nullable();
Map(x => x.TTEXDESCJERAR_EU).Column("TTEXDESCJERAR_EU").Not.Nullable();
Table("TARLLJERAR");
To sum it up, if one of the foreign key items is id = 0, it wont set the foreign key but will insert a new item on aux table
How can i solve this issue?
Thanks in advance!

NHibernate - How to QueryOver in joined table with restrictions

I am stuck with a SQL query (using NHibernate 4).
I have 2 tables (Client and Technology) with many-to-many relationship so I created a junction table called ClientTechnology.
I am trying to retrieve all the Technologies available (that are non-custom) PLUS all the Technologies available (that are custom) and belong to a given Client.
In SQL this is the statement:
declare #clientId int = 1
select * from
[dbo].[Technology] t
where t.IsCustom = 0
union
select t.* from
[dbo].[Technology] t
join [dbo].[ClientTechnology] ct
on ct.TechnologyId = t.Id
where t.IsCustom = 1 and ct.ClientId = #clientId
My Fluent Mapping for Client table is:
public ClientMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
}
For Technology table is:
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
Map(x => x.IsCustom).Not.Nullable();
HasMany(x => x.ClientTechnologies)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Table("ClientTechnology")
.KeyColumn("TechnologyId");
}
and finally the junction table ClientTechnology:
public ClientTechnologyMap()
{
Id(x => x.Id);
Map(x => x.Alias).Not.Nullable();
Map(x => x.IsDeleted).Not.Nullable();
References<Client>(x => x.Client, "ClientId");
References<Technology>(x => x.Technology, "TechnologyId");
}
I a open to different options to achieve this.
Assuming I have available a Client object (the ClientId)
I could retrieve first a list of Technologies that match the requirement IsCustom = false
and then retrieve a list of Technologies that match the requirement
IsCustom = true AND "the provided client is the owner of this custom technology"
Within a method public IEnumerable<Technology> GetTechnologies(Client client) that must return the enumerable of Technology (given a Client instance)
I have tried the following to retrieve globalTechnologies:
var globalTechnologies = _session.QueryOver<Technology>()
.WhereNot(x => x.IsDeleted)
.WhereNot(x => x.IsCustom)
.List();
And the following for customTechnologies whose owner is the client:
Technology technology = null;
ClientTechnology clientTechnology = null;
var customTechnologies = _session.QueryOver<Technology>(() => technology)
.JoinAlias(() => technology.ClientTechnologies, () => clientTechnology)
.WhereNot(x => x.IsDeleted)
.Where(x => x.IsCustom)
.Where(clientTechnology.Client == client) //this doesn't compile
.List();
but I don't know how to access the junction table (joined) in order to apply the restriction.
Any help would be much appreciated. Thank you.
In your case, the only problem is, that you do not provide Expression inside of the .Where(), so this should do the job:
// instead of this
// .Where(clientTechnology.Client == client) //this doesn't compile
// use this
.Where(() => clientTechnology.Client == client)
But I would go even farther. We should be able to creat subquery, which
will return only such Techonology.Id which are belonging to client.
we then can use also OR and have one query which would either select these who are:
NOT IsCustom or
Do belong to Client
How to create subquery you can see here:
How to do a QueryOver in Nhibernate on child collection
And example with OR
Use OR Clause in queryover in NHibernate
NHibernate QueryOver with WhereRestriction as OR

Fluent NHibernate bulk data insert

I need to transfer some data to other db, but when I start this mssql server freeze.
ds2 row count about 250,000. What am I doing wrong?
every time I try same funcion first looks working and after give error on random row. maybe 5, 95 or 280. until error records looks valid.
string inlineSql = "Select * from album";
DataSet ds2 = SqlHelper.ExecuteDataset(ConnectionString, CommandType.Text, inlineSql);
for (int i = 0; i < ds2.Tables[0].Rows.Count; i++)
{
Entity.Album.Album album = new Entity.Album.Album();
album.AlbumName = ds2.Tables[0].Rows[i]["AlbumName"].ToString();
album.WebSite = (Entity.Artist.WebSite)Convert.ToInt16(ds2.Tables[0].Rows[i]["WebSite"]);
album.Year = ds2.Tables[0].Rows[i]["Year"].ToString();
Entity.Artist.Artist artist = Entity.Artist.Artist.READ.ById(Convert.ToInt32(ds2.Tables[0].Rows[i]["ArtistId"]));
if (artist != null)
{
artist.AddAlbum(album);
album.Save();
}
}
AddAlbum Method
public virtual void AddAlbum(Album.Album album)
{
album.Artist = this;
AlbumList.Add(album);
}
Save Method
using (var session = NHibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(x);
transaction.Commit();
}
}
Error on session.SaveOrUpdate(x);
Error :
An exception of type 'NHibernate.Exceptions.GenericADOException'
occurred in NHibernate.dll but was not handled in user code
Additional information: could not insert: [Entity.Album.Album][SQL:
INSERT INTO [Album]
(AlbumName, WebSite, Year, ArtistId) VALUES (?, ?, ?, ?); select
SCOPE_IDENTITY()]
AlbumMap
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
Id(x => x.AlbumId);
Map(x => x.AlbumName);
Map(x => x.WebSite).CustomType<short>();
Map(x => x.Year);
HasMany(x => x.LyricList).Table("Lyric").KeyColumn("AlbumId").Cascade.All().Not.LazyLoad();
References(x => x.Artist).Column("ArtistId").ForeignKey("ArtistId").Fetch.Join();
}
}
ArtistMap
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
Id(x => x.ArtistId);
Map(x => x.ArtistName);
Map(x => x.ArtistURL);
Map(x => x.ImgName);
Map(x => x.ImgURL);
Map(x => x.Alfabet);
Map(x => x.SeoLink);
Map(x => x.WebSite).CustomType<short>();
HasMany(x => x.AlbumList).Table("Album").KeyColumn("ArtistId").Cascade.All().Not.LazyLoad();
}
}
More Exception
The INSERT statement conflicted with the FOREIGN KEY constraint
"FKA0CE20AA48AC4CAD". The conflict occurred in database "LYRICSWEB",
table "dbo.Artist", column 'ArtistId'. The statement has been
terminated.
There are some indications, which should lead to the answer. We can see that the error is:
Additional information: could not insert: [Entity.Album.Album][SQL: INSERT INTO [Album]
(AlbumName, WebSite, Year, ArtistId) VALUES (?, ?, ?, ?); select SCOPE_IDENTITY()]
while we are loading artist correct way...
Entity.Artist.Artist artist = Entity.Artist.Artist.READ
.ById(Convert.ToInt32(ds2.Tables[0].Rows[i]["ArtistId"]));
The issue would be in incorrect collection mapping. The reference from Albumt to Artist seem to be correct:
public AlbumMap()
{
..
References(x => x.Artist)
.Column("ArtistId")
.ForeignKey("ArtistId")
.Fetch.Join();
}
But the other end of that relation is mapped like this:
// wrong
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("AlbumId") // wrong
.Cascade.All().Not.LazyLoad();
}
Both above relations are the same! They are only mapped from different ends (from collection item perspective and from collection owner perespective).
That means that the mapping column must be the same, i.e. not "AlbumId" but "ArtistId"
// correct
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("ArtistId") // this is the relation
.Cascade.All().Not.LazyLoad();
}
Because NHibernate loads some artist, and its collection of AlubmList.. it could lead to some unexpected insert statements... fix of that mapping should help...
Second thing to mention: Inverse()
We are working with Album instances... and we are assigning their relation to collection holder - Artist. This is good and suggested way how to do that. Good.
But we can profit from NHibernate feature called Inverse() which will reduce few SQL statements... and make all the insert more simple... so extend your Artist mapping like this:
// correct
public ArtistMap()
{
..
HasMany(x => x.AlbumList)
.Table("Album")
.KeyColumn("ArtistId") // this is the relation
.Cascade.All()
.Not.LazyLoad()
.Inverse(); // ESSENTIAL improvement of generated SQL
}
Check this for more details

Assign a part of a composite foreign key at runtime

I have a class that references another class with a composite Id:
SingleIdClassMap(){
Id(x=>x.Id);
References(x=>CompositeIdClass);
}
CompositeIdClass(){
CompositeId().KeyReference(x => x.SingleIdClass).KeyReference(x => x.DynamicProperty);
}
Now this does not compile since in the SingleIdClassMap, there is no information about the DynamicProperty. I want this to be loaded from another class at runtime:
PropertyClass.Singleton.GetCurrentProperty();
Is there a way to tell NHibernate that it can retrieve the value for the second part of the composite key from PropertyClass.GetCurrentProperty()?
IMO a filter is easiest
EntityMap()
{
Id(x => x.Id);
HasMany(x => Texts)
.KeyColumn("entity_id")
.ApplyFilter("languageFilter", "language_id = :lid");
}
EntityTextClass()
{
CompositeId()
.KeyReference(x => x.Entity, "entity_id")
.KeyProperty(x => x.LanguageId);
}
// at beginning of request
session.EnableFilter("languageFilter").SetParameter(":lid", languageId);
var entity = session.Query<Entity>().Fetch(e => e.Texts).First();
string text = entity.Texts.First(); // could be a seperate property
or if you need all Texts (e.g. When reviewing/editing localization)
var entity = session.Query<Entity>().Fetch(e => e.Texts).First();
var allTexts = entity.Texts;

NHibernate fluent update problem (one to many relation)

I have a object OtherFaclilityEntity which contains a IList of OtherFcs objects, and the mapping is as follows:
public OtherFacilityMap()
{
Schema("SOME");
Table("OTHER_FACILITY");
Id(x => x.Id, "OTHER_FACILITY_S").GeneratedBy.TriggerIdentity();
Map(x => x.RowCreator, "ROW_CREATOR");
Map(x => x.RowCreateDate, "ROW_CREATE_DATE");
Map(x => x.Description, "DESCRIPTION");
Map(x => x.ExistenceKdNm, "R_EXISTENCE_KD_NM");
References(x => x.FacilityClassItem, "FACILITY_CLASS_S").LazyLoad(Laziness.False).Fetch.Join().Not.Insert().Not.Update();
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Not.LazyLoad();
}
When i try to do a SaveOrUpdate on the OtherFacility entity, it also updates all the entities in the FacilityCmList, which is fine, but in the last sql that is run tries to remove all relations between the parent and the child objects:
NHibernate.SQL: 2011-07-19 10:29:33,111 [361] DEBUG NHibernate.SQL [(null)] - UPDATE SOME.FACILITY_CMS SET WHOLE_S = null WHERE WHOLE_S = :p0;:p0 = '26021842'
I assume it has something to do with my mapping, any ideas?
After reading NHibernate sets Foreign Key in secondary update rather than on initial insert violating Not-Null Constrain on key column i found that i needed to add Inverse to the HasMany relation.
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Inverse().Not.LazyLoad();

Categories

Resources