I have simple 3 POCO classes:
public class User
{
//PK
public virtual int UserId { get; set; }
//ONE to ONE
public virtual Profil Profil{ get; set; }
//ONE to MANY
public virtual IList<PhotoAlbum> Albums { get; set; }
}
public class Profil
{
//PK
public virtual int ProfilId { get; set; }
public virtual int Age { get; set; }
public virtual int Sex { get; set; }
}
public class PhotoAlbum
{
//PK
public virtual int PhotoAlbumId { get; set; }
public virtual string Name { get; set; }
public virtual int NumberOfPhoto { get; set; }
}
I created these mapping classes:
public class UserMap : ClassMap<User>
{
public UserMap()
{
//PK
Id(p => p.UserId)
.GeneratedBy.Identity();
//FK
References(p => p.Profil)
.Column("ProfilId")
.Cascade.All();
//ONE TO MANY
HasMany(p => p.Albums)
.Cascade.All();
Table("Users");
}
}
public class ProfilMap: ClassMap<Profil>
{
public ProfilMap()
{
Id(p => p.ProfilId)
.GeneratedBy.Identity();
Map(p => p.Age)
.Not.Nullable();
Map(p => p.Sex)
Table("Profiles");
}
}
public class PhotoAlbumMap : ClassMap<PhotoAlbum>
{
public PhotoAlbumMap()
{
Id(p => p.PhotoAlbumId)
.GeneratedBy.Identity();
Map(p => p.Name)
.Not.Nullable();
Map(p => p.NumberOfPhoto)
.Not.Nullable();
Table("PhotoAlbums");
}
}
Then I created simple NHibernate repository class with this method:
public IList<T> GetItemsByCriterions(params ICriterion[] criterions)
{
ICriteria criteria = AddCriterions(_session.CreateCriteria(typeof(T)),
criterions);
IList<T> result = criteria.List<T>();
return result ?? new List<T>(0);
}
For test I created repository for some entity, for example User:
_userRepo = new NHibRepository<User>(NHibeHelper.OpenSession());
and I would like have possibility make query in this style:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Gt("Profile.Age",10)
});
this attempt finished with error:
could not resolve property: Profile of: Repository.User
User has property Profile type of Profile and this property has properties ProfileId, Age
and sex.
** #1 EDITED:**
# I tried this:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Where<User>(u=>u.Profil.Sex==0)
});
finished with error:
could not resolve property: Profil.Sex of: Repository.User
#2 EDITED
I tried use Nathan’s advice:
var result = _userRepo.Session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Sex", 0));
IList<User> users=null;
if (result != null)
users = result.List<User>();
If I tried convert result to List I again get this error: could not resolve property: Profile of: Repository.User
Looking at your example, User has a Profil property not a Profile property.
If it is supposed to be Profil then I would change the Restrictions.Gt(Profile.Age,10) to Restrictions.Gt(Profil.Age,10) otherwise change the name of the property and mapping to match the query.
Edit:
You are trying to query the User Object. you need to include the CreateAlias let nhibernate know that you want to link to a different object.
Try This.
var users = session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Age", 10));
Related
I'm trying to query on a simple data structure in nhibernate and MSSQL
dbo.Projects : Id(int, not null)
dbo.Finances : Id(int, not null), ProjectId(int,not null), foreign key references to dbo.projects
I want to get all the records in projects table that are not present in finances table where the finances table has a foreign key reference ProjectId.
I am migrating to (Fluent) Nhibernate 3 from EntityFramework?
//So far I have got here:
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance()
{
var factory = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2008
.ConnectionString(m_connectionString))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<ProjectMap>()
).BuildSessionFactory();
using (var session = factory.OpenSession())
{
var allprojects = session.QueryOver<ProjectModel>();
var projectsToReturn = allprojects.List<ProjectModel>().AsQueryable();
//--- Something like : all the records not in finances table ---------
// .Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
// .Select(project => new ProjectModel
// {
// Id=project.Id,
// ProjectName = project.ProjectName,
// });
return projectsToReturn;
}
}
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.ProjectModel);
}
}
//-------------------------------------------------------
//This is an Equivalent working code Using EntityFramework :
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance() {
IQueryable<ProjectModel> projectList = db.Projects
.Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
.Select(project => new ProjectModel
{
Id=project.Id,
ProjectName = project.ProjectName,
});
return projectList;
}
//-------------------------------------------------------
On second thought, you may try this without changing anything to your mapping, using a subquery :
var notOrphanProjectIdsSubquery = QueryOver.Of<FinanceModel>()
.Select(x => x.ProjectId);
var orphanProjects = session.QueryOver<ProjectModel>()
.WithSubquery
.WhereProperty(x=>x.Id)
.NotIn(notOrphanProjectIdsSubquery)
.List();
----------------------- Initial answer
Assuming you have a mapped Finances Property in your Project class, and according to https://stackoverflow.com/a/14980450/1236044, it should be something like :
var orphanProjects = session.QueryOver<ProjectModel>()
.WhereRestrictionOn(x => x.Finances).IsEmpty()
.List();
I must confess I am not proficient with FluentNH. I guess the classes and mappings should be something like this, hoping I'm not setting you on the wrong track...
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
public virtual ProjectModel Project{get;set;}
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
public virtual IList<FinanceModel> Finances { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
HasMany(x => x.Finances);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.Project);
}
}
i'm trying to remove an item from a one to many list and have it persist in the database. Here are the entities i have defined:
public class SpecialOffer
{
public virtual int SpecialOfferID { get; set; }
public virtual string Title { get; set; }
public virtual IList<SpecialOfferType> Types { get; private set; }
public SpecialOffer()
{
Types = new List<SpecialOfferType>();
}
}
public class SpecialOfferType
{
public virtual SpecialOffer SpecialOffer { get; set; }
public virtual Type Type { get; set; }
public virtual int MinDaysRemaining { get; set; }
#region composite id requirements
public override bool Equals(object obj)
{
if (obj == null || !(obj is SpecialOfferType))
return false;
var t = (SpecialOfferType)obj;
return SpecialOffer.SpecialOfferID == t.SpecialOffer.SpecialOfferID && Type.TypeID == t.Type.TypeID;
}
public override int GetHashCode()
{
return (SpecialOffer.SpecialOfferID + "|" + Type.TypeID).GetHashCode();
}
#endregion
}
public class Type
{
public virtual int TypeID { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
}
With the following fluent mappings:
public class SpecialOfferMap : ClassMap<SpecialOffer>
{
public SpecialOfferMap()
{
Table("SpecialOffers");
Id(x => x.SpecialOfferID);
Map(x => x.Title);
HasMany(x => x.Types)
.KeyColumn("SpecialOfferID")
.Inverse()
.Cascade.All();
}
}
public class SpecialOfferTypeMap : ClassMap<SpecialOfferType>
{
public SpecialOfferTypeMap()
{
Table("SpecialOfferTypes");
CompositeId()
.KeyReference(x => x.SpecialOffer, "SpecialOfferID")
.KeyReference(x => x.Type, "TypeID");
Map(x => x.MinDaysRemaining);
}
}
public class TypeMap : ClassMap<Type>
{
public TypeMap()
{
Table("Types");
Id(x => x.TypeID);
Map(x => x.Title);
Map(x => x.Price);
}
}
The problem i have is that if i remove an item from the SpecialOffer.Types collection it successfully removes it from the list but when i try to save the session the change is not persisted in the database. I'm assuming this is something to do with the composite id on the join table since i have been able to do this successfully in the past with a standard id.
I'd appreciate it if someone could show me what i'm doing wrong. Thanks
I think you have to 1) Change the cascade setting on SpecialOffer.Types to Cascade.AllDeleteOrphan() and 2) set SpecialOfferType.SpecialOffer = null when you remove it from the collection. Since the collection is the inverse side of the relationship, the many-to-one reference to SpecialOffer on SpecialOfferType has to be set to null to make it an orphan, then Cascade.AllDeleteOrphan will cause it to be deleted.
My application has the following entities:
public class User
{
public virtual int UserID { get; set; }
public virtual string UserName { get; set; }
public virtual IList<UserLog> Log { get; private set; }
public User()
{
Log = new List<UserLog>();
}
}
public class UserLog
{
public virtual int UserLogID { get; set; }
public virtual User User { get; set; }
public virtual string UserName { get; set; }
public virtual DateTime DateCreated { get; set; }
}
With the following fluent mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
HasMany(x => x.Log)
.KeyColumn("UserID")
.OrderBy("DateCreated")
.Inverse()
.Cascade.All();
}
}
public class UserLogMap : ClassMap<UserLog>
{
public UserLogMap()
{
Table("UsersLog");
Id(x => x.UserLogID);
References(x => x.User, "UserID");
Map(x => x.UserName);
Map(x => x.DateCreated);
}
}
The UsersLog table simply logs any changes which are made to the User. I'm trying to hook this up automatically using the NHibernate event listeners. I have setup my configuration which successfully calls the following 2 methods:
public void OnPostInsert(PostInsertEvent #event)
{
if (#event.Entity is User)
InsertLog(#event.Entity);
}
public void OnPostUpdate(PostUpdateEvent #event)
{
if (#event.Entity is User)
InsertLog(#event.Entity);
}
Edit (here is the InsertLog method):
private void InsertLog(object entity)
{
var context = ServiceLocator.Current.GetInstance<IDataContext>();
var user = (User)entity;
context.Repository<UserLog>().Insert(new UserLog
{
User = user,
UserName = user.UserName,
DateCreated = DateTime.UtcNow
}); // Insert calls ISession.SaveOrUpdate(entity)
}
When i update a record a log is successfully inserted but when i insert a record i get an error telling me the UserID cannot be null in the UsersLog table. I've played around with a few different variations but nothing seems to work.
I'd really appreciate it if someone could show me how this can be done. Thanks
Solution posted in question comments. I simply had to use:
#event.Session.GetSession(NHibernate.EntityMode.Poco).Save(...);
to save the user instead of accessing the repository.
I have the following tables in my database:
Announcements:
- AnnouncementID (PK)
- Title
AnouncementsRead (composite PK on AnnouncementID and UserID):
- AnnouncementID (PK)
- UserID (PK)
- DateRead
Users:
- UserID (PK)
- UserName
Usually I'd map the "AnnouncementsRead" using a many-to-many relationship but this table also has an additional "DateRead" field.
So far I have defined the following entities:
public class Announcement
{
public virtual int AnnouncementID { get; set; }
public virtual string Title { get; set; }
public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }
public Announcement()
{
AnnouncementsRead = new List<AnnouncementRead>();
}
}
public class AnnouncementRead
{
public virtual Announcement Announcement { get; set; }
public virtual User User { get; set; }
public virtual DateTime DateRead { get; set; }
}
public class User
{
public virtual int UserID { get; set; }
public virtual string UserName { get; set; }
public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }
public User()
{
AnnouncementsRead = new List<AnnouncementRead>();
}
}
With the following mappings:
public class AnnouncementMap : ClassMap<Announcement>
{
public AnnouncementMap()
{
Table("Announcements");
Id(x => x.AnnouncementID);
Map(x => x.Title);
HasMany(x => x.AnnouncementsRead)
.Cascade.All();
}
}
public class AnnouncementReadMap : ClassMap<AnnouncementRead>
{
public AnnouncementReadMap()
{
Table("AnnouncementsRead");
CompositeId()
.KeyReference(x => x.Announcement, "AnnouncementID")
.KeyReference(x => x.User, "UserID");
Map(x => x.DateRead);
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
HasMany(x => x.AnnouncementsRead)
.Cascade.All();
}
}
However when I run this I receive the following error:
"composite-id class must override Equals(): Entities.AnnouncementRead"
I'd appreciate it if someone could point me in the right direction. Thanks
You should do just what NHibernate is telling you. AnnouncementRead should override Equals and GetHashCode methods. They should be based on fields that are part of primary key
When implementing equals you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
More technically: You should follow the Liskows Substitution Principle and ignore symmetricity.
The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.
http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html
I am using Fluent NHibernate to map the following classes:
public abstract class DomainObject
{
public virtual int Id { get; protected internal set; }
}
public class Attribute
{
public virtual string Name { get; set; }
}
public class AttributeRule
{
public virtual Attribute Attribute { get; set; }
public virtual Station Station { get; set; }
public virtual RuleTypeId RuleTypeId { get; set; }
}
public class Station : DomainObject
{
public virtual IList<AttributeRule> AttributeRules { get; set; }
public Station()
{
AttributeRules = new List<AttributeRule>();
}
}
My Fluent NHibernate mappings look like this:
public class AttributeMap : ClassMap<Attribute>
{
public AttributeMap()
{
Id(o => o.Id);
Map(o => o.Name);
}
}
public class AttributeRuleMap : ClassMap<AttributeRule>
{
public AttributeRuleMap()
{
Id(o => o.Id);
Map(o => o.RuleTypeId);
References(o => o.Attribute).Fetch.Join();
References(o => o.Station);
}
}
public class StationMap : ClassMap<Station>
{
public StationMap()
{
Id(o => o.Id);
HasMany(o => o.AttributeRules).Inverse();
}
}
I would like to order the AttributeRules list on Station by the Attribute.Name property, but doing the following does not work:
HasMany(o => o.AttributeRules).Inverse().OrderBy("Attribute.Name");
I have not found a way to do this yet in the mappings. I could create a IQuery or ICriteria to do this for me, but ideally I would just like to have the AttributeRules list sorted when I ask for it.
Any advice on how to do this mapping?
I think the OrderBy-method takes in the string that it inserts to the generated SQL-clause. So just doing
HasMany(o => o.AttributeRules).Inverse().OrderBy("Name");
Where the "Name" is the name of the column that contains Attribute's name. It should be in the column list because Attribute is joined to the AttributeRule.
Did you solve this other way? Please share.