NH3.2 Mapping By Code using 'where' clause - nhibernate

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");
}));

Related

NHibernate Removing ManyToMany record when deleting parent

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")))
});

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>

Nhibernate query issue

Considering the following mapping file where TemporaryAccessOpenCommand and TemporaryAccessCloseCommand both inherit from base class Command
<class name="Command" table="xxx_Commands" lazy="false">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="BeginDate" />
<property name="EndDate" />
<component name="Result" class="CommandResult">
<property name="Status" column="ResultStatus"/>
<property name="Details" column="ResultDetails" />
</component>
<many-to-one name="Requestor" class="xxx.Domain.SessionInfo, xxx.Domain" column="SessionInfoId" lazy="false" />
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Close">
<key column="CommandId"/>
</joined-subclass>
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessOpenCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Open">
<key column="CommandId"/>
<many-to-one name="EndUser" ...... />
<property name="Reason"/>
<property name="AccessRight"/>
<property name="AccessType"/>
<bag name="CloseAccessCommands" cascade="all" lazy="false">
<key column="OpenCommandId"/>
<one-to-many class="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess"/>
</bag>
</joined-subclass>
What would be the nhibernate query to retrieve every OpenAccessCommand having no succeeded CloseAccessCommand ?
I tried this :
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.CreateCriteria("CloseAccessCommands")
.Add(Expression.Not(Expression.Eq("Result.Status", CommandStatus.Succeeded)))
.List<TemporaryAccessOpenCommand>();
});
}
But it will return an OpenAccessCommand that has tow CloseCommand (one failed and one succeeded) when it should return an empty list.
Thanks for your help (and excuse my poor english)
I managed to get something working this way (I'm open to suggestions if it's not the best way)
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
string sql = string.Format(#"
(
SELECT temp_c.OpenCommandId
FROM VSA2_Commands_TemporaryAccess_Close temp_c
INNER JOIN VSA2_Commands c
ON temp_c.CommandId = c.Id
WHERE c.ResultStatus = {0}
) AS OpenCommandId"
, (int)CommandStatus.Succeeded);
var subCriteria = DetachedCriteria.For<TemporaryAccessCloseCommand>();
subCriteria = subCriteria.SetProjection(Projections.SqlProjection(sql, new string[] { "OpenCommandID" }, new IType[] { NHibernateUtil.Int32 }));
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.Add(Expression.Eq("Result.Status", CommandStatus.Succeeded))
.Add(Subqueries.PropertyNotIn("Id", subCriteria))
.List<TemporaryAccessOpenCommand>();
});
}

NHibernate: Persist an object with children

I am trying to persist an object with a collection of child objects. I can't persist the children first as there is a FK relationship. I could save the parent first and then add the children on to it, but this would introduce more work. Basically I'm just trying to save a fully populated object in one step and not break it into parts. Is there something wrong with my mapping (sorry it looks so ugly) or is it my methods?
Parent:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NetworkOrderManagement.Core.Order, NetworkOrderManagement.Core" table="NETORDMGMT.ORDERHEADER" lazy="false" >
<id name="OrderId" column="ORDERID" type="int">
<generator class="seqhilo">
<param name="sequence">ORDERID_SEQ</param>
</generator>
</id>
<property name="TransmissionDate" column="TRANSMISSIONDATE" type="DateTime"/>
<property name="StoreNumber" column="STORENUMBER" type="Int16"/>
<property name="Department" column="DEPARTMENT" type="Int16"/>
<property name="OrderType" column="ORDERTYPE" type="Int16"/>
<property name="OrderSequence" column="ORDERSEQUENCE" type="Int16"/>
<property name="ExtractTime" column="EXTRACTTIME" type="DateTime"/>
<property name="Status" column="STATUS" type="Int16"/>
<property name="ReceivedTime" column="RECEIVEDTIME" type="DateTime"/>
<bag name="OrderDetail" table="NETORDMGMT.ORDERDETAIL"
lazy="false" cascade="all" inverse="true">
<key column="ORDERID" on-delete="cascade"/>
<one-to-many class="NetworkOrderManagement.Core.OrderDetail, NetworkOrderManagement.Core" />
</bag>
</class>
</hibernate-mapping>
Child:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NetworkOrderManagement.Core.OrderDetail, NetworkOrderManagement.Core" table="NETORDMGMT.ORDERDETAIL" lazy="false">
<id name="OrderDetailId" column="ORDERDETAILID" type="int">
<generator class="seqhilo">
<param name="sequence">"ORDERDTLID_SEQ"</param>
</generator>
</id>
<many-to-one name="Order" class="NetworkOrderManagement.Core.Order, NetworkOrderManagement.Core"
column="OrderId" lazy="false" not-null="true" />
<property name="ItemNumber" column="ITEMNUMBER" type="Int32"/>
<property name="OrderQuantity" column="ORDERQUANTITY" type="Int32"/>
<property name="ErrorCode" column="ERRORCODE" type="Int32"/>
</class>
</hibernate-mapping>
Here's my exception:
Test method NetworkOrderManagement.Tests.DataAccess.QuickTests.QuickTest threw exception:
Distribution.Exceptions.DataAccessException: NHibernate Exception --->
NHibernate.PropertyValueException: not-null property references a null or transient valueNetworkOrderManagement.Core.OrderDetail.Order.
I get this when my test below tries to add an orderdetail to the order while it is still transient:
[TestMethod]
public void QuickTest()
{
myOrderRepository = NetworkOrderManagement.Data.RepositoryFactory.Instance.GetOrderRepository();
myOrderDetailRepository = NetworkOrderManagement.Data.RepositoryFactory.Instance.GetOrderDetailRepository();
myOrder = new Order { StoreNumber = RandGen.LittleRand(), Department = RandGen.LittleRand(), TransmissionDate = DateTime.MinValue, ExtractTime = DateTime.MinValue, ReceivedTime = DateTime.MinValue };
myOrder = myOrderRepository.Save(myOrder);
myOrderDetail1 = new OrderDetail {OrderId = myOrder.OrderId, ItemNumber = RandGen.BigRand(), OrderQuantity = RandGen.LittleRand() };
myOrderDetail2 = new OrderDetail {OrderId = myOrder.OrderId, ItemNumber = RandGen.BigRand(), OrderQuantity = RandGen.LittleRand() };
myOrderDetail1 = myOrderDetailRepository.Save(myOrderDetail1);
myOrderDetail2 = myOrderDetailRepository.Save(myOrderDetail2);
myOrder.OrderDetail.Add(myOrderDetail1);
myOrder.OrderDetail.Add(myOrderDetail2);
myOrderRepository.CommitChanges();
myOrderDetailRepository.Delete(myOrderDetail2);
myOrderRepository.CommitChanges();
myOrderRepository.Delete(myOrder);
myOrderRepository.CommitChanges();
}
Specify cascading on the collection, and let NHibernate figure it out for you
http://ayende.com/Blog/archive/2006/12/02/NHibernateCascadesTheDifferentBetweenAllAlldeleteorphansAndSaveupdate.aspx
http://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/example-parentchild.html
Okay, I've seen that you've done that. :)
What you haven't done, is specify the back-reference.
I mean: you add an item to your collection, but this added item has a property to its owner, which you haven't set:
Order o = new Order();
OrderDetail detail = new OrderDetail ();
detail.Order = o;
o.OrderLines.Add (detail);
What would be even better (imho) is this (simplified) :
public class Order
{
private IList<OrderDetail> _details = new List<OrderDetail>();
public ReadOnlyCollection<OrderDetail> Details
{
return new List(_details).AsReadOnly();
}
public void AddOrderLine( OrderDetail d )
{
d.Order = this;
_details.Add (d);
}
public void RemoveOrderLine( OrderDetail d )
{
_details.Remove (d);
}
}

Querying with NHibernate

I am new to NHibernate and I am trying to learn how to query my data.
Below is the configuration xml. Only the recipe is shown.
I want to be able to query recipes by recipetitle from keywords entered
and also ingredients from ingredientname.
So you might enter "pasta wine" for example.
This is what I have tried but gives me an error.
hql = "from Recipe r " +
"left join r.Images " +
"inner join r.User " +
"inner join r.Ingredients i " +
"where i.IngredientName Like '%pasta%' OR i.IngredientName Like '%wine%' OR r.RecipeTitle Like '%pasta' OR r.RecipeTitle Like '%wine%'";
I want to eager load the collections as well.
Am I going about querying right??
I need to able to build the query string from my search criteria.
This would be easy form me in SQL.
Malcolm
<class name="Recipe" table="Recipes" xmlns="urn:nhibernate-mapping-2.2">
<id name="RecipeID" type="Int32" column="RecipeID">
<generator class="identity" />
</id>
<property name="RecipeTitle" type="String">
<column name="RecipeTitle" />
</property>
<property name="Completed" type="Boolean">
<column name="Completed" />
</property>
<property name="ModifiedOn" type="DateTime">
<column name="ModifiedOn" />
</property>
<property name="Rating" type="Double">
<column name="Rating" />
</property>
<property name="PrepTime" type="Int32">
<column name="PrepTime" />
</property>
<property name="CookTime" type="Int32">
<column name="CookTime" />
</property>
<property name="Method" type="String">
<column name="Method" />
</property>
<bag name="Images" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.RecipeImage, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
<many-to-one name="Category" column="CategoryID" />
<bag name="Comments" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.Comment, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
<many-to-one name="User" column="EnteredByID" />
<bag name="Ingredients" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.Ingredient, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
</class>
To build dynamic queries, I would use the criteria API. This makes the dynamic query much more stable, because you don't need string operations to build it up.
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.CreateCriteria("Ingredients", "i", JoinType.InnerJoin)
.Add(
Expression.Disjunction() // OR
.Add(Expression.Like("i.IngredientName", "%pasta%"))
.Add(Expression.Like("i.IngredientName", "%wine%"))
.Add(Expression.Like("r.RecipeTitle", "%pasta%"))
.Add(Expression.Like("r.RecipeTitle", "%wine%")));
List<Recipe> result = query.List<Recipe>();
Edit:
For eager loading you could set the fetch-mode:
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.SetFetchMode("Images", FetchMode.Join)
.SetFetchMode("Comments", FetchMode.Join)
.SetFetchMode("Ingredients", FetchMode.Join)
But I wouldn't do this because you get the results multiplied by the number of Images, Comments and Ingredients. So if you had 4 Images, 2 Comments and 12 Ingredients, you get your recipe 96 times. You don't recognize this, because NHibernate puts the things together again, but it generates traffic between the application and the database. So better let NHibernate load it with separate queries.
One more edit to show dynamic query composition.
// filter arguments, all are optional and should be omitted if null
List<string> keywords;
TimeSpan? minCookingTime;
TimeSpan? maxCookingTime;
int? minRating;
int? maxRating;
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r");
if (keyword != null)
{
// optional join
query.CreateCriteria("Ingredients", "i", JoinType.InnerJoin);
// add keyword search on ingredientName and RecipeTitle
var disjunction = Expression.Disjunction();
foreach (string keyword in keywords)
{
string pattern = String.Format("%{0}%", keyword);
disjunction
.Add(Expression.Like("i.IngredientName", pattern))
.Add(Expression.Like("r.RecipeTitle", pattern));
}
query.Add(disjunction)
}
if (minCookingTime != null)
{
query.Add(Expression.Ge(r.CookingTime, minCookingTime.Value));
}
if (maxCookingTime != null)
{
query.Add(Expression.Le(r.CookingTime, maxCookingTime.Value));
}
if (minRating != null)
{
query.Add(Expression.Ge(r.Rating, minRating.Value));
}
if (maxRating != null)
{
query.Add(Expression.Le(r.Rating, maxRating.Value));
}
Both Stefan's and Sathish's examples concatenate % operators into the SQL. This is unnecesary as Restrictions.Like (nhib 2.0+) and Expression.Like (before v2.0) have 3 parameter versions with a MatchMode.
Disjunction keywordsCriteria = Restrictions.Disjunction();
foreach (var keyword in keywords)
{
keywordsCriteria.Add(Restrictions.Like("i.IngredientName", keyword, MatchMode.Anywhere));
keywordsCriteria.Add(Restrictions.Like("r.RecipeTitle", keyword, MatchMode.Anywhere));
}
Full text queries are also available with NHibernate Search. See Ayende's example for more details.
Here is the above criteria with dynamic keywords
string searchQuery = "wine pasta";
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.CreateCriteria("Ingredients", "i", JoinType.InnerJoin)
.SetFetchMode("Images", FetchMode.Join)
.SetFetchMode("Comments", FetchMode.Join)
.SetFetchMode("Ingredients", FetchMode.Join)
.SetResultTransformer(new DistinctRootEntityResultTransformer());
var keywords = searchQuery.Split(' ');
Disjunction keywordsCriteria = Restrictions.Disjunction();
foreach (var keyword in keywords)
{
keywordsCriteria.Add(Restrictions.Like("i.IngredientName", string.Format("%{0}%", keyword)));
keywordsCriteria.Add(Restrictions.Like("r.RecipeTitle", string.Format("%{0}%", keyword)));
}
query.Add(keywordsCriteria);
List<Recipe> result = query.List<Recipe>();