NHibernate Removing ManyToMany record when deleting parent - nhibernate

i'm working with legacy code(so i'd like to change it as little as possible), and i'm having a little trouble with a many to many relationship here.
Here's what i have:
public class Feature {
List<Profile> Profiles{get;set;}
}
public class Profile{
List<FeatureProfile> Features{get;set;}
}
public class FeatureProfile {
Feature Feat {get;set;}
Profile Profile {get;set;}
}
and their mapping is like this:
mapper.Class<Feature>(m=>
m.Bag(x => x.Profiles, bagMap =>
{
bagMap.Table("FeatureProfile");
bagMap.Key(key=>key.Column("FeatureId"));
bagMap.Cascade(Cascade.All | Cascade.DeleteOrphans);
},map=>map.ManyToMany(manyToMany=>manyToMany.Column("ProfileId")))
);
mapper.Class<Profile>(m=>
m.Bag(x => x.Features, bagMap =>
{
bagMap.Key(key=>key.Column("ProfileId"));
bagMap.Inverse(true);
bagMap.Cascade(Cascade.All | Cascade.DeleteOrphans);
})
);
mapper.Class<FeatureProfile>(m=> {
m.ManyToOne(x => x.Profile, x => x.Column("ProfileId"));
m.ManyToOne(x => x.Feat, x => x.Column("FeatureId"))
});
What I need is: When I delete a Feature, it's FeatureProfile's get deleted too.
Note that I think this probably worked in NHibernate 2.x

My experience is more with XML mapping, but I would say that the below rows could help you anyway. NHibernate provides direct mapping for m:n reletaions (with Pair-table, as in example above). You can totally remove the object FeatureProfile. The relation will be mapped implicitly, and the same will apply when deleting any of both ends (Profile or Feature)
<class name="Feature"...>
...
<bag name="Profiles" lazy="true"
table="FeatureProfile" cascade="none" >
<key column="FeatureId" />
<many-to-many class="Profile" column="ProfileId" />
</bag>
...
<class name="Profile"...>
...
<bag name="Features" lazy="true"
table="FeatureProfile" cascade="none" >
<key column="ProfileId" />
<many-to-many class="Feature" column="FeatureId" />
</bag>
...
And in this case, NHibernate has no other way, when deleting Feature, then to delete Pair-table records as well (cannot/should not leave DB inconsistent).
EDITED:
cascade for a bag should be none in this case. delete-orphan would cause really dangerous deletion: not only the pairs but also the ends of that relation.
EDIT BY OP: Here's what we'd have using Mapping by Code:
mapper.Class<Profile>(m =>
{
m.Bag(x => x.Features, bagMap =>
{
bagMap.Table("FeatureProfile");
bagMap.Key(key=>key.Column("ProfileId"));
bagMap.Lazy(CollectionLazy.Lazy)
bagMap.Inverse(false);
bagMap.Cascade(Cascade.None);
},map=>map.ManyToMany(manyToMany=>manyToMany.Column("FeatureId")))
}
mapper.Class<Feature>(m =>
{
m.Bag(x => x.Profiles, bagMap =>
{
bagMap.Table("FeatureProfile");
bagMap.Key(key=>key.Column("FeatureId"));
bagMap.Lazy(CollectionLazy.Lazy)
bagMap.Inverse(false);
bagMap.Cascade(Cascade.None);
},map=>map.ManyToMany(manyToMany=>manyToMany.Column("ProfileId")))
});

Related

NHibernate: Error when saving Bag with NOT-Nullable Constraint

Given are tables Item and ItemTranslation where the latter has a NOT-Nullable foreign key on Item.
ItemTranslation.hbm.xml only has its properties Text and LanguageCode, it does NOT map Item.
Item.hbm.xml:
<bag name="Translations" cascade="all-delete-orphan">
<key column="ItemID" />
<one-to-many class="ItemTranslation, SomeNamespace" />
</bag>
Now when I do the following:
Item item = new Item();
item.Translations.Add( new ItemTranslation { LanguageCode = "DE", Text = "Test DE" } );
item.Translations.Add( new ItemTranslation { LanguageCode = "EN", Text = "Test EN" } );
item.Save();
NHibernate throws the following exception:
System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ItemID', table 'someDb.dbo.ItemTranslation'; column does not allow nulls. INSERT fails.
Cannot insert the value NULL into column 'ItemID', table 'someDb.dbo.ItemTranslation'; column does not allow nulls.
I could fix it either by mapping ItemID as many-to-one in IssuedItemTranslation.hbm.xml or making the column NULLable.
But both ways are kind of ugly.
Is there any other possibility? Maybe with some change in the bag-mapping?
Thank you in advance.
If we do not want to use inverse mapping (see below) .. we must left the column in DB nullable.
That is how it works. NHibernate will
insert children (or parent)
insert parent (or children) read more here 9.6. Flush
update children with parent id
Another way: Inverse mapping
There is nothing bad on explicit Item back reference mapping on ItemTranslatin. I do that always. And If we really do not like it.. it could be protected property.
But then, we would need inverse mapping.
<bag name="Translations" cascade="all-delete-orphan" inverse="true">
And also set the reference on both sides.
Item item = new Item();
var tr1 = new ItemTranslation {
LanguageCode = "DE",
Text = "Test DE"
Item = item } ;
item.Translations.Add(tr1);
...
This solution does not require item column to be nullable...
That would be the way I suggest. Read more here:
Minimal and correct way to map one-to-many with NHibernate
It works when mapping the bag as follows:
<bag name="Translations" cascade="all-delete-orphan">
<key column="ItemID" not-null="true" update="false" />
<one-to-many class="ItemTranslation, SomeNamespace" />
</bag>
Is this the correct solution?

Can you create a Restriction for a Detached Criteria on a primitive collection?

I would like to know if there is a way to create a Restriction on a primitive collection of a Model in NHibernate.3.3.3?
Here's the details:
class Parent {
IEnumerable<string> ChildNames { get; set; }
}
I need to search like so:
private DetachedCriteria BuildQuery() {
var inNames = { "Bob", "Sam", "Dan" };
var query = DetachedCriteria.For<Parent>("parent");
query.Add(Restrictions.In("ChildNames", inNames));
return query;
}
I found this old question that says it's not possible, but given the fact that it's old and doesn't have a ton of upvotes, I'd like to confirm before I refactor.
If I can do it and I'm totally botching it, I'll take that help as well!
In this scenario, we can use Projection (something less type-safe, then mapped Property, but more flexible).
Let's expect the mapping like this:
<bag name="ChildNames" inverse="false" lazy="true" table="[dbo].[ChildNames]"
cascade="all"
batch-size="25">
<key column="ParentId" />
<element type="System.String" column="ChildName" />
</bag>
Then we can adjust the Build query method like this:
protected virtual DetachedCriteria BuildQuery()
{
var inNames = new [] { "Bob", "Sam", "Dan" };
// parent query reference
var query = DetachedCriteria.For<Parent>("parent");
// reference to child query
var child = query.CreateCriteria("ChildNames");
// let's project the column name of the Element, e.g. 'Name'
var columnNameProjection = Projections.SqlProjection(
"ChildName as name", null, new IType[] { NHibernateUtil.String }
);
// in clause
child.Add(Restrictions.In(
columnNameProjection, inNames
));
return query;
}
And this is what we will get:
SELECT ...
FROM Parent this_
inner join [dbo].[ChildNames] childNames3_
on this_.ParentId=childNames3_.ParentId
WHERE ChildName in (#p0, #p1, #p2)
...
#p0=N'Bob',#p1=N'Sam',#p2=N'Dan'
The caveat:
While this is in deed working... the ChildName is used without the Alias. That could be pretty tricky to fulfill... so be careful, if there are more columns with the name ChildName in this scenario
I ended up, like many, refactoring the collection into a strong type.

NHibernate - Left joins

I have the following two tables:
Jobs AreaID, JobNo (composite key)
Logs LogID, AreaID, JobNo
I need to get all jobs that don't have any logs associated with them. In SQL I could do:
SELECT Jobs.AreaID,
Jobs.JobNo
FROM Jobs
LEFT JOIN Logs
ON Jobs.AreaID = Logs.AreaID
AND Jobs.JobNo = Logs.JobNo
WHERE Logs.LogID is null
But I'm not sure how to accomplish this with NHibernate. Could anyone offer any pointers?
Here are my mappings:
<class name="Job" table="Jobs">
<composite-key name="Id">
<key-property name="JobNo"/>
<key-many-to-one name="Area" class="Area" column="AreaID"/>
</composite-key>
</class>
<class name="Log" table="Logs">
<id name="Id" column="LogID">
<generator class="identity"/>
</id>
<property name="JobNo"/>
<many-to-one name="Area" class="Area" column="AreaID"/>
</class>
Thanks
Update
OK, I modified Nosila's answer slightly, and this is now doing what I wanted:
Log logs = null;
return session.QueryOver<Job>()
.Left.JoinAlias(x => x.Logs, () => logs)
.Where(x => logs.Id == null)
.List<Job>();
I also had to add this to my Job mapping:
<bag name="Logs">
<key>
<column name="JobNo"></column>
<column name="DivisionID"></column>
</key>
<one-to-many class="Log"/>
</bag>
Thanks for the help. :)
I'm not familiar with composite identifiers as I don't use them so for all I know NHibernate will automatically create the proper left join. None the less, the (non-tested) query below should get you started.
Job jobAlias = null;
Log logAlias = null;
YourDto yourDto = null;
session.QueryOver<Job>()
// Here is where we set what columns we want to project (e.g. select)
.SelectList(x => x
.Select(x => x.AreaID).WithAlias(() => jobAlias.AreaID)
.Select(x => x.JobNo).WithAlias(() => jobAlias.JobNo)
)
.Left.JoinAlias(x => x.Logs, () => logAlias, x.JobNo == logAlias.JobNo)
.Where(() => logAlias.LogID == null)
// This is where NHibernate will transform what you have in your `SelectList()` to a list of objects
.TransformUsing(Transformers.AliasToBean<YourDto>())
.List<YourDto>();
public class YourDto
{
public int AreaID { get; set; }
public int JobNo { get; set; }
}
Note: You need NHibernate 3.2 in order to set join conditions.
Job job = null;
var jobsWithoutLogs = session.QueryOver(() => job)
.WithSubquery.WhereNotExists(QueryOver.Of<Log>()
.Where(log => log.Job == job)
.Select(Projections.Id()))
.List()
Update: i saw you added the mapping. The Above Code only works for the following mapping
<class name="Log" table="Logs">
<id name="Id" column="LogID">
<generator class="identity"/>
</id>
<many-to-one name="Job" >
<column name="JobNo"/>
<column name="AreaID"/>
<many-to-one />
</class>

NH3.2 Mapping By Code using 'where' clause

I've tried to define many-to-many relation with 'where' clause using MappingByCode from NH3.2, but I don't know how can I do it.
With FluentNHibernate I can use the ChildWhere() method:
public class ProcedureMap : ClassMap<Procedure>
{
public ProcedureMap()
{
this.HasManyToMany(a => a.FormTemplates).ChildWhere("IsDeleted = 0").AsSet();
}
}
This code will generate next HBM:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" name="Procedure" table="Procedure">
<set name="FormTemplates" table="ProceduresToFormTemplates">
<key foreign-key="FK_Procedures_FormTemplates">
<column name="ProcedureId" />
</key>
<many-to-many class="FormTemplate" where="IsDeleted = 0">
<column name="FormTemplateId" />
</many-to-many>
</set>
</class>
</hibernate-mapping>
How can I get same mapping using MappingByCode from NH3.2?
You would use the filter method on the many to many mapping.
this.Bag(
x => x.Procedure,
m =>
{
m.Table("Procedure");
m.Key(k => k.Column("ProcedureId"));
m.Filter("NoDeleted", mapper => mapper.Condition("IsDeleted = 0"));
},
x => x.ManyToMany(
map =>
{
map.Column("FormTemplateId");
}));

nHibernate join query

i have two tables (graph representation - one table is nodes, other is link between nodes), and i want to write this query in nHibernate:
SELECT
distinct(t.id), t.NodeName, e.Fk_linkOne, e.Fk_linkTwo, e.RelationName
FROM Nodes t
INNER JOIN NodeRelation e ON t.Id=e.Fk_linkOne OR t.Id=e.Fk_linkTwo
where (e.Fk_linkOne =84 OR e.Fk_linkTwo=84 ) AND t.Id!=84
I did not find how to connect two tables wiht join, that have OR in it..
ICriteria criteriaSelect = Session
.CreateCriteria(typeof(NodeRelation ), "nodeRelations")
.CreateCriteria("nodeRelations.Node", "node",
NHibernate.SqlCommand.JoinType.InnerJoin)
You can only define your joins as per the associations you have defined in your mappings. As far as I know you can't define OR style relationships in Nhibernate. Consider using a self referential style graph representation.
public class Node
{
public IList<Node> Parents { get; set; }
public IList<Node> Children { get; set; }
}
<bag name="Parents" table="Node_Relation">
<key column="ChildId" />
<many-to-many class="Node" column="ParentId" />
</bag>
<bag name="Children" table="Node_Relation">
<key column="ParentId" />
<many-to-many class="Node" column="ChildId" />
</bag>
You should be using DetachedCriteria to get the same done. I am not sure about your query but I will jsut give a shot.
var dc1= DetachedCriteria.For(typeof( NodeRelation )).Add(Restrictions.Eq("Fk_linkOne", 84))
.SetProjection(Projections.Property("Fk_linkOne"));
var dc2= DetachedCriteria.For(typeof( NodeRelation )).Add(Restrictions.Eq("Fk_linkTwo", 84))
.SetProjection(Projections.Property("Fk_linkTwo"));
Session.CreateCriteria(typeof(Nodes))
.Add(Subqueries.PropertyIn("Id", dc1))
.Add(Subqueries.PropertyIn("Id", dc2))
.Add(Restrictions.Eq("Id", 84)).List<Nodes>;
Hope the above query is corrrect. please let me know if you cant get it to work after obs trying smethings and let me know wat u tried.
It always depends on your classes and not so much on your tables. Remember, you are using a ORM and you are working with a class model.
Assumed that your classes look like this:
class Node
{
List<Node> Relations { get; private set; }
List<Node> InverseRelations { get; private set; }
}
You may map it like this:
<class name="Node">
<!-- .... -->
<bag name="Relations" table="NodeRelation">
<key name="Fk_linkOne">
<many-to-many class="Node" column="Fk_linkTwo"/>
</bag>
<bag name="InverseRelations" table="NodeRelation" inverse="true">
<key name="Fk_linkTwo">
<many-to-many class="Node" column="Fk_linkOne"/>
</bag>
</class>
This way you get asymetrical relations (this means: when Node A relates to Node B, B isn't necessarily related to A, except of the InverseRelation of course). I don't know what you actually need, so this is an assumption based on your database design.
Your query may look like this:
from Node n
where
:x in elements(n.Relations)
or :x in elements(n.InverseRelations)
Note: x is an entity type, not just an id (you need to load it using session.Load<Node>(84)),
Another way for the samy query:
select distinct n
from Node n
inner join n.Relations e1
inner join n.InverseRelations e2
where e1.id = 84 or e2.id = 84
Or another way without the use of the inverse relations:
select n
from Node n, Node n2 inner join n.Relations e
where
(n = n2 and e.id = 84)
OR (n = e and n2.id = 84)
In criteria I would take the second solution and write it like this:
session.CreateCriteria<Node>("n")
.SetProjection(Projections.Distinct("n"))
.CreateCriteria("Relations", "e1")
.CreateCriteria("InverseRelations", "e2")
.Add(Expression.Or(
Expression.Eq("e1.id", 84),
Expression.Eq("e2.id", 84));