I'm trying to get setup to use nhibernate against our oracle database.
One of my goals is to test query logic. I started off using sqlite, but ran into problems with many-to-one relationships no pulling in foreign object. Thinking this was a sqlite problem I set up a new oracle database to test against, but I get the same symptoms.
When I run a query against a populated database, it works fine and returns results which properly lazy load foreign objects as they are referenced in code.
the query logic tests(contained with in a mstest assembly) return nulls instead of foreign object.
The differences between the two querys:
1. The test query creates,saves and flushes the data just before
calling the query.
2. The test configuration includes steps needed to export the mapped schema
into test database, uses a different default schema and connection string.
3. The test query happens in a mstest unit test assembly.
I have confirmed:
1. That running query against real database works even when run from the unit test
assembly.
2. That the test data is being save in the test database schema.
3. That the foreign key reference is created in the test database.
4. That the primary keys are being created in both tables used in query.
I suspect that im missing a step when saving the data to the test database, so I will share that code, I can add the configuration code if requested.
var session = DbSession.Load(ConnModes.UnitTest);
var uil = new List<UserInfo>();
uil.Add(new UserInfo { Id = -1, FullName = "Fred Flintstone", LoginName = "fredflint" });
uil.Add(new UserInfo { Id = -1, FullName = "Barney Ruble", LoginName = "barnrubl" });
uil.Add(new UserInfo { Id = -1, FullName = "Bam Bam", LoginName = "bambam" });
foreach (var f in uil)
{
session.Save(f);
}
var rightNow = DateTime.Now;
var x = new List<FacilityRouteFormula>();
x.Add(new FacilityRouteFormula { Id = -1, TrackingFacilityId = 2, Formula = "a formula1", ComponentGroupName = "Comp 0", UserId = 1, CreationDate = rightNow.AddMinutes(5) });
x.Add(new FacilityRouteFormula { Id = -1, TrackingFacilityId = 2, Formula = "a formula2", ComponentGroupName = "Comp 0", UserId = 1, CreationDate = rightNow.AddMinutes(3) });
x.Add(new FacilityRouteFormula { Id = -1, TrackingFacilityId = 2, Formula = "a formula3", ComponentGroupName = "Comp 0", UserId = 1, CreationDate = rightNow.AddMinutes(4) });
x.Add(new FacilityRouteFormula { Id = -1, TrackingFacilityId = 2, Formula = "a formula4", ComponentGroupName = "Comp 0", UserId = 1, CreationDate = rightNow.AddDays(1) });
x.Add(new FacilityRouteFormula { Id = -1, TrackingFacilityId = 2, Formula = "a formula5", ComponentGroupName = "Comp 0", UserId = 1, CreationDate = rightNow });
foreach (var f in x)
{
session.Save(f);
}
session.Flush();
var uidata2 = (from uid in session.Query<UserInfo>() select uid).ToList();
var data = (from uid in session.Query<FacilityRouteFormula>() select uid).ToList();
var q = (from frf in session.Query<FacilityRouteFormula>()
where frf.ComponentGroupName == "Comp 0" &&
frf.TrackingFacilityId == 2
orderby frf.CreationDate descending
select frf).ToList();
Assert.IsNotNull(q[0].User);
Assert.AreEqual("fredflint", q[0].User.LoginName);
Here is the query against "real" database.
var s = DbSession.Load(ConnModes.Dev );
var q = (from frf in s.Query<FacilityRouteFormula>()
where frf.ComponentGroupName == "Comp 0" &&
frf.TrackingFacilityId == 2
orderby frf.CreationDate descending
select frf).ToList();
mapping files as requested:
FacilityRouteFormula:
<id name="Id" column="N_FACILITY_ROUTE_FORMULA_ID" type="long" unsaved-value="-1">
<generator class="native" >
<param name="sequence">SEQ_FACILITY_ROUTE_FORMULA</param>
</generator>
</id>
<property name="TrackingFacilityId" column="N_TRACKING_FACILITY_ID" type="long" />
<property name ="ComponentGroupName" column="C_COMPONENT_GROUP_NAME" type="string"/>
<property name="CreationDate" column="D_CREATION_DATE" type="DateTime"/>
<property name="UserId" column="N_USER_ID" type="int" not-null="true" />
<property name="Formula" column="C_FORMULA" type="string" not-null="true" />
<many-to-one insert="false" update="false" lazy="false" name="User" fetch="select">
<column name="N_USER_ID" sql-type="NUMBER" not-null="true" />
</many-to-one>
UserInfo:
<id name="Id" column="N_USER_ID" type="int" unsaved-value="-1" >
<generator class="native" >
<param name="sequence">SEQ_USER_ID</param>
</generator>
</id>
<property name="FullName" column="C_USER_FULL_NAME" type="string" not-null="true" />
<property name="LoginName" column="C_LOGIN_NAME" type="string" not-null="true" />
I think you need to clear the cache something like this.
session.Save(f);
session.Flush();
session.Evict(f);
Related
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>
I am getting the following exception.
NHibernate.PropertyValueException : not-null property references a null or transient
Here are my mapping files.
Product
<class name="Product" table="Products">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<set name="PriceBreaks" table="PriceBreaks" generic="true" cascade="all" inverse="true" >
<key column="ProductId" />
<one-to-many class="EStore.Domain.Model.PriceBreak, EStore.Domain" />
</set>
</class>
Price Breaks
<class name="PriceBreak" table="PriceBreaks">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="ProductId" column="ProductId" type="Int32" not-null="true" />
<many-to-one name="Product" column="ProductId" not-null="true" cascade="all" class="EStore.Domain.Model.Product, EStore.Domain" />
</class>
I get the exception on the following method
[Test]
public void Can_Add_Price_Break()
{
IPriceBreakRepository repo = new PriceBreakRepository();
var priceBreak = new PriceBreak();
priceBreak.ProductId = 19;
repo.Add(priceBreak);
Assert.Greater(priceBreak.Id, 0);
}
Following up on Jan reply. I've removed the ProductId from priceBreak map. This works!!
public int AddPriceBreak(Product product, PriceBreak priceBreak)
{
using (ISession session = EStore.Domain.Helpers.NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
product.AddPriceBreak(priceBreak);
session.SaveOrUpdate(product);
transaction.Commit();
}
return priceBreak.Id;
}
Remove the ProductId property from the mapping and from the PriceBreak class. And use the PriceBreaks collection to add PriceBreaks, you don't need the PriceBreakRepository, but only a ProductRepository.
Example:
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var product = session.Get<Product>(19);
product.AddPriceBreak(new PriceBreak());
tx.Commit();
}
}
And in the Product:
class Product
{
// ...
public void AddPriceBreak(PriceBreak pb)
{
pb.Product = this;
PriceBreaks.Add(pb);
}
}
Your usage of Id properties along with the actual references is incorrect.
First, remove this line:
<property name="ProductId" column="ProductId" type="Int32" not-null="true" />
Then, instead of assigning ProductId (you should remove that property completely), use:
priceBreak.Product = session.Load<Product>(19);
(You might need to add the Load method to your repository).
I'm trying to persist a SortedList<DateTime, double?> with NHibernate 2.0. When an item's value (not key) is null, that item is not persisted to the database.
Class snippet:
public class TimeSeries{
...
public TimeSeries(){
Data = new SortedList<DateTime, double?>();
}
public virtual IDictionary<DateTime, double?> Data { get; private set; }
...
}
Mapping snippet:
...
<map name="Data" cascade="all" lazy="true" sort="natural" collection-type="sorted-list">
<key column="ID"/>
<index type="Date" column="Period"/>
<element type="double" column="Value" not-null="false"/>
</map>
...
Usage snippet:
var series = new TimeSeries();
series.Data.Add(new DateTime(2000, 1, 1), 1);
series.Data.Add(new DateTime(2000, 1, 2), null);
series.Data.Add(new DateTime(2000, 1, 3), 3);
repository.Add(series);
The first and third items are persisted, but not the second. If I change the double value from a null value to a non-null value, it saves fine.
Any idea how I can save the null values? I'm using MySql and the database schema allows for nulls.
Thanks,
Marcus
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);
}
}
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>();