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.
Related
I dont normally deal with data like this but I thought id give it a try. As it turned out I failed :/ and am not sure how to proceed.
I have a database object track:
public virtual string Type { get; set; }
public virtual IList<DateTypeTrack> TrackDates { get; set; }
With a mapping file:
Table("Tracks");
Map(x => x.Type).Not.Nullable();
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All();
The DateTypeTrack Object looks like this:
public virtual DateType DateType { get; set; }
public virtual Track Track { get; set; }
public virtual int Days { get; set; }
With a mapping file like this:
Table("DateTypeTracks");
References(x => x.DateType, "DateTypeID").Not.Nullable();
References(x => x.Track, "TrackID").Not.Nullable();
Map(x => x.Days).Not.Nullable();
If its necessary, Ill post the DateType code aswell, but I dont think its needed.
And am trying to write a delete method in my service layer that is pretty simple:
public void PushDelete(int id)
{
Track track = _tracks.Get(id);
try
{
_tracks.BeginTransaction();
_tracks.Delete(track);
_tracks.CommitTransaction();
}
catch (Exception)
{
_tracks.RollbackTransaction();
throw;
}
}
I keep getting an error:
could not delete collection: [TSE.Domain.DatabaseObjects.Track.TrackDates#12][SQL: UPDATE DateTypeTracks SET TrackID = null WHERE TrackID = #p0]
I dont know why its trying to do the update at the end, but I suppose that is what is causing the issue. What sort of recourse do I have?
Thanks.
since the DateTypeTrack already cares for the association between the two entities you should mark the HasMany as Inverse to tell NH that the hasmany does not maintain it (the Update)
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All().Inverse();
I'm using fluent hibernate 1.2.0.712 and nhibernate 3.2.0.4000 in my mvc project as an OR mapper, the problem is :
this is my oJob object:
public class Job{
virtual public Enquiry Enquiry { get; set; }
virtual public long Id { get; set; }
}
and this is Enquiry:
public class Enquiry {
virtual public long Id { get; set; }
}
and here is JobMap:
public class JobMap: ClassMap<Job>
{
public JobMap()
{
Schema("dbo");
Id(p => p.Id)
.Column("Id");
References(p => p.Enquiry);
}
}
i expect that each job has exactly one enquiry
but sometimes that i check sql server i see there are some records in job table with different ids that all have the same enquiryid
and i checked it many times and dont know when exactly it happens, what is the problem?
You must change your mapping : change
References(p => p.Enquiry);
by
HasOne(p => p.Enquiry);
Im fairly n00bish when it comes to fluent nhibernate but i have an unexpected error in one of my repositories.
I have a datatype CostCode
public class CostCode
{
public virtual int Id { get; set; }
public virtual String CostCodeCode { get; set; }
public virtual Company Company { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual String CreatedBy { get; set; }
public virtual DateTime ModifiedDate { get; set; }
public virtual String ModifiedBy { get; set; }
}
and here is the mapping
public sealed class CostCodeMap : ClassMap<CostCode>
{
/**
* #breif Mapping Constructor
*/
public CostCodeMap()
{
Id(Reveal.Member<CostCode>("Id"));
Map(x => x.CostCodeCode).Not.Nullable();
References(x => x.Company, "CompanyId").Cascade.All();
Map(x => x.CreatedDate).Not.Nullable();
Map(x => x.CreatedBy).Not.Nullable();
Map(x => x.ModifiedDate).Not.Nullable();
Map(x => x.ModifiedBy).Not.Nullable();
}
}
When i try to update this, i get an error "identifier of an instance of Domain.DataTypes.Company was altered from 1 to 8"
Now i think its the way that i set up the mapping, and possibly how my repository is handling the updates/adds.
I have a drop down list that controls the id of the company, and when im adding/updating i set the property company to whatever is in the database for the id that it has been updated to.
var companyRepository= new CompanyRepository(_session);
temp.Company = companyRepository.GetCompanyById(temp.Company.Id);
_session.Update(c);
Can anyone give me a hint/solution to help me on my way? Looking through related problems here, the problem could be anything.
Ok, I will just throw this out... I bet what is happening is you are setting temp.Company.Id by changing the Id, then you use the repo to go fetch that company using the changed Id. NHibernate will track that you changed the Id on the other company however. Use a temp var to store that new company id, dont change the id of the other company.
I'm not 100% sure, but it really looks like maybe there is a bug here:
temp.Company = ...(temp.Company.Id);
I would figure you'd actually be pulling that from an incoming parameter.
Also, you can avoid a database hit here by using Session.Load():
temp.Company = _session.Load<Company>(passedInCompanyId);
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.
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.