I am trying to figure out what I thought was just a simple one to many mapping using fluent Nhibernate. I hoping someone can point me to the right directory to achieve this one to many relations
I have an articles table and a categories table
Many Articles can only belong to one Category
Now my Categores table has 4 Categories and Articles has one article associated with cateory1
here is my setup.
using FluentNHibernate.Mapping;
using System.Collections;
using System.Collections.Generic;
namespace FluentMapping
{
public class Article
{
public virtual int Id { get; private set; }
public virtual string Title { get; set; }
public virtual Category Category{get;set;}
}
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class ArticleMap:ClassMap<Article>
{
public ArticleMap()
{
Table("Articles");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Title);
References(x => x.Category).Column("CategoryId").LazyLoad();
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
}
}
}
}
if I run this test
[Fact]
public void Can_Get_Categories()
{
using (var session = SessionManager.Instance.Current)
{
using (var transaction = session.BeginTransaction())
{
var categories = session.CreateCriteria(typeof(Category))
//.CreateCriteria("Articles").Add(NHibernate.Criterion.Restrictions.EqProperty("Category", "Id"))
.AddOrder(Order.Asc("Description"))
.List<Category>();
}
}
}
I am getting 7 Categories due to Left outer join used by Nhibernate
any idea what I am doing wrong in here?
Thanks
[Solution]
After a couple of hours reading nhibernate docs I here is what I came up with
var criteria = session.CreateCriteria(typeof (Category));
criteria.AddOrder(Order.Asc("Description"));
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
var cats1 = criteria.List<Category>();
Using Nhibernate linq provider
var linq = session.Linq<Category>();
linq.QueryOptions.RegisterCustomAction(c => c.SetResultTransformer(new DistinctRootEntityResultTransformer()));
var cats2 = linq.ToList();
I don't really know what's the problem, because I don't know how you save the categories, but it might be caused by using the wrong cascade setting in the mapping?
Using Join on a HasMany is unusual; it's typically used on References, the many side of a one-to-many relationship. Instead of the solution you came up with, you should lazy load the collection or use Fetch.Select. Both will cause NH to issue two selects, one to load the Category and another to load its associated Articles.
Addendum:
The error you're getting is pretty straight-forward: the collection can't be loaded because the ISession that was used to load the parent is out of scope (or its connection was closed). Setting the fetch mode to Select will resolve this (I think, I haven't tried it). So your collection mapping would be:
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Select();
If you can keep the ISession open I would recommend lazy loading:
HasMany(x => x.Articles).KeyColumn("CategoryId").LazyLoad();
It's unusual to use Join on a collection mapping due to the problem you ran into. Issuing a join from the one side will return a parent object for each object in the collection, just as it would in SQL.
Related
I'm having trouble persisting primitive type collection using (Fluent)NHibernate.
Here's the entity and mapping:
public class SomeOne
{
public virtual long ID { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Iesi.Collections.Generic.ISet<string> Foo { get; protected set; }
public SomeOne()
{
Foo = new HashedSet<string>();
}
}
public SomeOneMap()
{
Id(x => x.ID).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
HasMany(x => x.Foo).Element("Code").AsSet().Not.Inverse();
Table("SomeTypeOne");
}
However, when I try to save SomeOne instance, associated Foo strings get's ignored.
var session = factory.OpenSession();
var one = new SomeOne();
one.Foo.Add("Dato");
one.Foo.Add("Mari");
session.Save(one);
Any idea what could be wrong?
Thanks
UPDATE
Here's db schema. it's generated by NH.
There are two ways of ensuring your collected is persisted.
Call session.Flush(); after session.Save(one);. This will cause NHibernate to persist your collection. More information on flush can be found here.
Wrap the whole thing in a transaction, i.e.
using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var one = new SomeOne();
one.Foo.Add("Dato");
one.Foo.Add("Mari");
session.Save(one);
transaction.Commit();
}
There are several reasons why option two is better than option one. The main advantage is that all objects are saved back to the DB within the same transaction, so if one insert fails then the transaction is rolled back and your DB is not left in an inconsistent state. The acceped answer to this question has an extensive explanation of why you should use transactions with NHibernate.
i'd like to query a one to many relationship on an entity. To help explain my problem further, imagine my application has the following entities:
public class User {
public virtual int UserID { get; set; }
public virtual Membership CurrentMembership {
get { return Membership.Single(m => m.IsValid); }
}
public virtual IList<Membership> Membership { get; private set; }
public User() {
Membership = new List<Membership>();
}
}
public class Membership {
public virtual int MembershipID { get; set; }
public virtual User User { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual DateTime? EndDate { get; set; }
public virtual int DaysLeft { get; set; }
public virtual bool IsValid { get; set; }
public Membership() {
}
}
With the following Fluent mapping (the mapping is kind of irrelevant, i've only put this here for guidance):
public class UserMap : ClassMap<User> {
public UserMap() {
Table("Users");
Id(x => x.UserID);
HasMany(x => x.Membership)
.KeyColumn("UserID")
.Inverse()
.Cascade.All();
}
}
public class MembershipMap : ClassMap<Membership> {
public MembershipMap() {
Table("Membership");
Id(x => x.MembershipID);
References(x => x.User);
Map(x => x.StartDate);
Map(x => x.DaysLeft)
.Formula("CASE WHEN EndDate IS NOT NULL AND dbo.DayDiff(GETUTCDATE(), EndDate) > 0 THEN dbo.DayDiff(GETUTCDATE(), EndDate) ELSE 0 END");
// DayDiff is a udf which gets the number of days between two dates
Map(x => x.IsValid)
.Formula("CASE WHEN dbo.GetValidMembershipID(UserID) = MembershipID THEN 1 ELSE 0 END");
// GetValidMembershipID is a udf which works out the valid membership id for this user
}
}
As you can see a User can have many Memberships. The CurrentMembership (property against the user) returns the Membership against the User where the IsValid property is true (this will only be true for a single membership against the user).
Now i'd like to be able to grab all users who's current membership has less than 20 days remaining.
My first attempt was to say:
session.Linq<User>().Where(u => u.CurrentMembership.DaysLeft < 20).ToList();
But this threw the error:
"could not resolve property: CurrentMembership of: User"
This was kind of expected since i didn't use a formula mapping for this property (like i did for the DaysLeft and IsValid properties against the membership). I can't see how you can use formula mapping other than to map strings, ints and bools. Next i tried saying:
session.Linq<User>().Where(u => u.Membership.Single(m => m.IsValid).DaysLeft < 20).ToList();
But this threw the error:
"Object reference not set to an instance of an object."
I know i could query this Membership directly but i've used this as an example of something i do in alot of places. Could anyone suggest an alternative way of mapping the CurrentMembership property which allows me to query it with Linq. Please note that i need optimum performance so converting to a list and working in memory will not suffice.
I'd appreciate the help. Thanks
You are using the old LINQ provider in NHibernateContrib for NHibernate 2.1.
This provider is no longer supported nor has any active effort in it.
The LINQ provider built into NHibernate 3.0 syntax is session.Query<TEntity>() instead of session.Linq<TEntity>().
Now NHibernate 3.0 is final release, so, if you had issues with it in the past, they're likely to have been solved already. I used both for heavy queries and the new one satisfies a lot of scenarios.
Maybe the best is to get the latest trunk source and use it.
I highly recommend you replace your stuff with NHibenrate 3.0. It's "almost" 100% backwards compatible with NHibernate 2.1, and all you should need to change is all session.Linq<TEntity>() to session.Query<TEntity>().
If it still doesn't work, you can try u.Membership.Any(...) or u.Membership.First(...) instead of u.Membership.Single(...) in the where condition.
If this still doesn't work, try to reverse the query. Query on Membership and select membership.User.
CurrentMembership will not work as it's not mapped.
How to force nhibernate to fully load many-to-many data in one sql query.
I have tried this:
var list = session.CreateCriteria<Q>("q")
.CreateAlias("q.PList", "p", JoinType.LeftOuterJoin)
.List();
But it loads only q.PList, and when I try to access q.PList[0].QList[0].PList[0] NH executes additional select query.
Entities and how I mapped them:
public class P
{
public virtual Guid Id { get; set; }
public virtual IList<Q> QList { get; set; }
}
public class Q
{
public virtual Guid Id { get; set; }
public virtual IList<P> PList{ get; set; }
}
public class PMap : ClassMap<P>
{
public PMap()
{
Table("p");
Id(t => t.Id);
HasManyToMany(t => t.QList)
.Table("q2p").ParentKeyColumn("pId").ChildKeyColumn("qId").Inverse();
}
}
public class QMap : ClassMap<Q>
{
public QMap()
{
Table("q");
Id(t => t.Id);
HasManyToMany(t => t.PList)
.Table("q2p").ParentKeyColumn("qId").ChildKeyColumn("pId");
}
}
I believe what you are really trying to do is:
var list = session.CreateCriteria<Q>()
.SetFetchMode("PList", FetchMode.Join)
.List();
Update based on your comment:
It's cumbersome and usually not worth it to try to fetch a whole graph with many collections using joins. For this particular case, I suggest that you use batch-size on the collections and let NH do a batched lazy-loading.
Depending on your code, it's also possible that a HQL query retrieves the data you want better than navigating the object graph.
or use HQL to achieve the same
your query will look as follows
var hqlQuery="select p from Q as q inner join fetch q.PList as p";
You run the query as follows:
Sesssion.CreateQuery(hqlQuery).List<P>();
Hope that helps.
I'm building an ecommerce site using S#arp Architecture.
I'm trying to map a hierachy of categories and retrieve the top level categories.
I'm using NHibernate.Linq for this.
I have the following entity:
public class Category : Entity
{
#region Properties
[DomainSignature]
[NotNullNotEmpty]
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual int ListOrder { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual IList<Category> ParentCategories { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
#endregion
public Category()
{
Products = new List<Product>();
ParentCategories = new List<Category>();
ChildCategories = new List<Category>();
}
}
with the following Fluent NHibernate mapping:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(p => p.Products)
.Cascade.All()
.Table("CategoryProduct");
HasManyToMany(c => c.ParentCategories)
.Table("CategoryHierarchy")
.ParentKeyColumn("Child")
.ChildKeyColumn("Parent")
.Cascade.SaveUpdate()
.AsBag();
HasManyToMany(c => c.ChildCategories)
.Table("CategoryHierarchy")
.ParentKeyColumn("Parent")
.ChildKeyColumn("Child")
.Cascade.SaveUpdate()
.Inverse()
.LazyLoad()
.AsBag();
}
}
I want to retrieve the root categories. I know I have eight in my db so here's my test:
[Test]
public void Can_get_root_categories()
{
// Arrange
var repository = new CategoryRepository();
// Act
var rootCategories = repository.GetRootCategories();
// Assert
Assert.IsNotNull(rootCategories);
Assert.AreEqual(8, rootCategories.Count());
}
I figure I just get all Categories where the ParentCategories list is empty (initialized in the ctor of Category). So here's my repository method:
public IQueryable<Category> GetRootCategories()
{
var session = NHibernateSession.Current;
// using NHibernate.Linq here
var categories = from c in session.Linq<Category>()
where c.ParentCategories.Count == 0
select c;
return categories;
}
When I run my test I get "NHibernate.QueryException : could not resolve property: ParentCategories.Id of: MyStore.Core.Category"
What am I doing wrong?
Here are related questions, but didn't quite solve my problem:
Fluent nHibernate: Need help with ManyToMany Self-referencing mapping
Querying a self referencing join with NHibernate Linq
Fluent NHibernate: ManyToMany Self-referencing mapping
Edit:
I think the problem lies with the .count in the Linq expression. This post related to this, but I'm not sure how to progress...
Got it. The problem was in the linq expression. It didn't like .Count for some reason. This might be a bug in NHibernate.Linq (NH2), but I hear NH3 linq is rock solid now.
Anyway, my solution is to use a Criteria instead:
var categories = session.CreateCriteria(typeof (Category))
.Add(Restrictions.IsEmpty("ParentCategories"))
.List();
Thanks to a blog post by nixsolutions.com for getting me on the right track. All green again.
You should be using the Count() extension method instead of the Count property. This works fine in NHibernate 2.
Regards
Jon
I'm trying to adopt Fluent NHibernate with my project, currently I can get data from database, when I'm at application server, data is include its PK but when I return this data (as List) to client all of its PK is loose.
How can I fixed this problem?
Update
My POCO class is below: PKs are CountryCd and CityCd
public class coCity
{
public virtual string CountryCd { get; private set; }
public virtual string CityCd { get; private set; }
public virtual string CityNameTH { get; set; }
public virtual string CityNameEN { get; set; }
public virtual int DeliveryLeadTime { get; set; }
public virtual string CreateBy { get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual string UpdateBy { get; set; }
public virtual DateTime UpdateDate { get; set; }
public override bool Equals(object obj)
{
return this.GetHashCode().Equals(obj.GetHashCode());
}
public override int GetHashCode()
{
return (this.CountryCd + this.CityCd).GetHashCode();
}
}
Mapping class:
public class coCityMap : ClassMap<coCity>
{
public coCityMap()
{
Table("coCity"); // this is optional
CompositeId()
.KeyProperty(x => x.CountryCd)
.KeyProperty(x => x.CityCd);
Map(x => x.CityNameTH);
Map(x => x.CityNameEN);
Map(x => x.DeliveryLeadTime);
Map(x => x.CreateBy);
Map(x => x.CreateDate);
Map(x => x.UpdateBy);
Map(x => x.UpdateDate);
}
}
Source code to get data at application server
public List<coCity> GetTest()
{
List<coCity> result = new List<coCity>();
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
result = (List<coCity>)session.CreateCriteria(typeof(coCity)).List<coCity>();
}
return result;
}
When its still at application server data is retrieve correctly as image below
alt text http://img138.imageshack.us/img138/1071/serverside.png
However when this data transit back to client side all of its PKs is loose like below.
alt text http://img203.imageshack.us/img203/1664/clientside.png
First of all, this isn't a problem with Fluent NHibernate so:
Serializable must be used on your POCO's when you serialize them.
(from your comment) NHibernate keeps a reference of the object retrieved from the database to a cache (1-st level cache). While you serialize this 'managed' object the output of the serialization is an unmanaged object. Nhibernate does not detect that a an object exists in the db just because you set an value in a newly constructed object. You must get the object from the database and update its properties and call Update() or you work with pure sql with the object that returned from the client (yikes!).
Note that is irrelevant with this question: your Equals() implementation is really bad as it doesn't take into account types and depends only on GetHashCode value. If all your classes have this implementation you could run into trouble.
I think the problem is with that private setter on the PK's properties. Try changing that to public.
Either way, mark your entity with Serializable
A few comments:
As a general recomendation when using nhibernate is to avoid composite Ids. Create on your model a surrogate Id that is an identity column and enforce uniqueness of CityCd and CountryCd somewhere else
When passing data around client/server tiers, consider using DTOs to avoid some commong LazyInitializationExceptions problems.