I have two classes:
public class Code
{
public virtual Guid CodeId { get; set; }
public virtual string CodeValue { get; set; }
public virtual Guid EntryId { get; set; }
}
public class Entry
{
public virtual Guid EntryId { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Address { get; set; }
public virtual string Address2 { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string Zip { get; set; }
public virtual string Email { get; set; }
public virtual string Phone { get; set; }
public virtual string IpAddress { get; set; }
public virtual DateTime BirthDate { get; set; }
public virtual DateTime Created { get; set; }
public virtual bool OptIn { get; set; }
public virtual Code Code { get; set; }
}
I want nhibernate to automatically load/save the Code child property of the Entry object (which can be null and is linked by a foreign key "EntryId" in the Codes table), but I cannot figure out the mapping. The documentation at hibernate.org isn't loading for me right now, so could someone point me in the right direction with the mappings below?
<class name="Code" table="Codes">
<id name="CodeId">
<generator class="guid.comb"/>
</id>
<property name="CodeValue" />
<property name="EntryId"
</class>
<class name="Entry" table="Entries">
<id name="EntryId">
<generator class="guid.comb"/>
</id>
<property name="FirstName" />
<property name="LastName" />
<property name="Address" />
<property name="Address2" />
<property name="City" />
<property name="State" />
<property name="Zip" />
<property name="Email" />
<property name="Phone" />
<property name="BirthDate" />
<property name="OptIn" />
<property name="IpAddress" />
<property name="Created" />
</class>
I believe this article describes the possibilities very well:
http://ayende.com/Blog/archive/2009/04/19/nhibernate-mapping-ltone-to-onegt.aspx
You just don't need to "simulate" the bidirectionality, but that's easy to leave out.
Related
I've spent a couple of days researching this on Google, StackOverflow and reading various blogs on this but to no avail. My question is if a collection is updated within an entity then would this cause NHibernate to update all properties in the entity of the modified collections? In this case I've added a user to a role and once I call session.SaveOrUpdate then 2 updates occur (NHibernate updates user and role) then the INSERT occurs. Is this the default behavior? I've tried to do the following to see if I can get NHibernate to just issue the INSERT statement:
Ran a Ghostbuster test on these entities based on code by Jason Dentler and Fabio Maulo but everything comes back ok and there are no dirty properties.
I made properties nullable that are defined as null in the database.
Set Inverse true on one of the entites.
Any help or insight is much appreciated.
Here are the mapping and class files.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Custom.NHibernateLib.Model" assembly="Custom.NHibernateLib" xmlns="urn:nhibernate-mapping-2.2">
<class name="Users">
<id name="UserId" type="Int64">
<generator class="hilo" />
</id>
<property name="ApplicationName" type="String" length="50" />
<property name="Username" type="String" length="15" />
<property name="Email" type="String" length="15" />
<property name="Password" type="String" length="50" />
<property name="PasswordSalt" type="String" length="128" />
<property name="PasswordQuestion" type="String" length="15" />
<property name="PasswordAnswer" type="String" length="50" />
<property name="IsApproved" type="YesNo" />
<property name="LastActivityDate" type="DateTime" />
<property name="LastLoginDate" type="DateTime" />
<property name="LastPasswordChangedDate" type="DateTime" />
<property name="CreationDate" type="DateTime" />
<property name="IsOnline" type="YesNo" />
<property name="IsAnonymous" type="YesNo" />
<property name="IsLockedOut" type="YesNo" />
<property name="LastLockedOutDate" type="DateTime" />
<property name="FailedPasswordAttemptCount" type="Int32" />
<property name="FailedPasswordAttemptWindowStart" type="DateTime" />
<property name="FailedPasswordAnswerAttemptCount" type="Int32" />
<property name="FailedPasswordAnswerAttemptWindowStart" type="DateTime" />
<property name="Comment" type="String" length="4001" />
<bag name="RoleList" table="UserRoles" lazy="true" cascade="save-update, persist" batch-size="10">
<key column="UserId" not-null="true" />
<many-to-many class="Roles" foreign-key="FK_Roles_UserRoles_RoleId">
<column name="RoleId" not-null="true" />
</many-to-many>
</bag>
<one-to-one name="Profiles" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Custom.NHibernateLib.Model" assembly="Custom.NHibernateLib" xmlns="urn:nhibernate-mapping-2.2">
<class name="Roles">
<id name="RoleId" type="Int64">
<generator class="hilo" />
</id>
<property name="ApplicationName" type="String" length="50" />
<property name="RoleName" type="String" length="50" />
<bag name="UserList" table="UserRoles" lazy="true" inverse="true" cascade="save-update, persist" batch-size="10">
<key column="RoleId" not-null="true" />
<many-to-many class="Users" foreign-key="FK_User_UserRoles_UserId">
<column name="UserId" not-null="true" />
</many-to-many>
</bag>
</class>
</hibernate-mapping>
Here are the class files:
using System;
using System.Collections.Generic;
namespace Custom.NHibernateLib.Model
{
public class Users : Entity
{
public Users (){}
public virtual long UserId { get; set; }
public virtual string ApplicationName { get; set; }
public virtual string Username { get; set; }
public virtual string Email { get; set; }
public virtual string Password{ get; set; }
public virtual string PasswordSalt { get; set; }
public virtual string PasswordQuestion { get; set; }
public virtual string PasswordAnswer { get; set; }
public virtual bool? IsApproved { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual DateTime? CreationDate { get; set; }
public virtual bool? IsOnline { get; set; }
public virtual bool? IsAnonymous { get; set; }
public virtual bool? IsLockedOut { get; set; }
public virtual DateTime? LastLockedOutDate { get; set; }
public virtual int? FailedPasswordAttemptCount { get; set; }
public virtual DateTime? FailedPasswordAttemptWindowStart { get; set; }
public virtual int? FailedPasswordAnswerAttemptCount { get; set; }
public virtual DateTime? FailedPasswordAnswerAttemptWindowStart { get; set; }
public virtual string Comment { get; set; }
public virtual IList<Roles> RoleList { get; set; }
public virtual Profiles Profiles { get; set; }
}
}
using System;
using System.Collections.Generic;
namespace Custom.NHibernateLib.Model
{
public class Roles : Entity
{
public Roles(){}
public virtual long RoleId { get; set; }
public virtual string ApplicationName { get; set; }
public virtual string RoleName { get; set; }
public virtual IList<Users> UserList { get; set; }
}
}
Adding the user and the role to their respective collections.
usr.RoleList.Add(role);
role.UserList.Add(usr);
When this is called session.SaveOrUpdate(role) then this occurs in NHibernate.
-- statement #1
UPDATE Users...WHERE UserId = 32768
-- statement #2
UPDATE Roles...WHERE RoleId = 65536
-- statement #3
INSERT INTO UserRoles...
Ok well I can conclude the my issue was caused by the way I was handling the session and commit after the save and update. I was closing the session then recreating it within multiple methods in my custom membership library code. For example, each method I was calling I was wrapping it around a using statement for the session and transaction. I should have known better and not go by my assumptions and just spend the time to RTM.
The NHibernate in Action book about session management and using current session context is what guided me. I coded that up to use in my unit test and everything worked fine. Though the book is a bit dated it still had some good basic info.
Referring to Ayende's post here:
http://ayende.com/blog/3941/nhibernate-mapping-inheritance
I have a similar situation that can be reached by extending the union-subclass mapping of the above post a bit, by adding an abstract Name-property to the Party. The model would be as follows:
public abstract class Party
{
public abstract string Name { get; }
}
public class Person : Party
{
public override string Name { get { return this.FirstName + " " + this.LastName; } }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Company : Party
{
public override string Name { get { return this.CompanyName; } }
public virtual string CompanyName { get; set; }
}
I'm looking for a mapping that would allow me to query over the parties in the following manner:
session.QueryOver<Party>().Where(p => p.Name.IsLike("firstname lastname")).List();
The mapping I'm using:
<class name="Party" table="`party`" abstract="true">
<id access="backfield" name="Id">
<column name="Id" />
<generator class="sequence">
<param name="sequence">party_id_seq</param>
</generator>
</id>
<union-subclass name="Person" table="`person`">
<property name="Name" formula="first_name || ' ' || last_name" update="false" insert="false" access="readonly">
</property>
<property name="FirstName">
<column name="first_name" />
</property>
<property name="LastName">
<column name="last_name" />
</property>
</union-subclass>
<union-subclass name="Company" table="`company`">
<property name="Name" access="readonly" update="false" insert="false">
<column name="company_name" />
</property>
<property name="CompanyName">
<column name="company_name" />
</property>
</union-subclass>
For both
session.QueryOver<Person>().Where(p => p.Name.IsLike("firstname lastname")).List();
and
session.QueryOver<Company>().Where(p => p.Name.IsLike("companyName")).List();
this behaves as I'd expect, and I can query over the name and get the matching results. However, when I do
session.QueryOver<Party>().Where(p => p.Name.IsLike("firstname lastname")).List();
The query doesn't match the Persons at all, but uses the mapping from the union-subclass of the company. So when parametrized with Party, the query seems to be essentially the same as when parametrized with Company (the WHERE-clause of the query is: WHERE this_.company_name = ((E'firstname lastname')::text))
Any pointers about where I might be going wrong and how to achieve what I'm after?
It would be because you were using the logic inside the properties, which NHibernate would be unable to determine. Since you have already defined the formulas for the Name field it's best to keep them as standard properties. So if you correct the entities as follows, it should work
public abstract class Party
{
public abstract string Name { get; }
}
public class Person : Party
{
public virtual string Name { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Company : Party
{
public virtual string Name { get; set; }
public virtual string CompanyName { get; set; }
}
Apparently this is a known issue of NHibernate 3.x:
https://nhibernate.jira.com/browse/NH-2354?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
Is there a way when executing a stored procedure with NHibernate to return an extra field that is not in the original model?
What I'm doing is creating a sproc to return search results of a Resource. It returns all of the normal fields of my Resource class, but also an extra field called Rank. Is there a way to map that on to the current Resource class (or even create a class that inherits from Resource and just adds that one property)?
The execution of my sproc is:
<sql-query name="GetRelatedResources">
<return alias="item" class="ResourceRanked">
<return-property column="Rank" name="Rank" />
<return-property column="ResourceId" name="Id" />
<return-property column="Name" name="Name" />
<return-property column="Description" name="Description" />
<return-property column="Filename" name="Filename" />
<return-property column="Filetype" name="Filetype" />
<return-property column="IsPublic" name="IsPublic" />
<return-property column="IsFeatured" name="IsFeatured" />
<return-property column="VideoEmbedCode" name="VideoEmbedCode" />
<return-property column="VideoId" name="VideoId" />
<return-property column="VideoPlayerId" name="VideoPlayerId" />
<return-property column="VideoPlayerKey" name="VideoPlayerKey" />
<return-property column="VideoHeight" name="VideoHeight" />
<return-property column="VideoWidth" name="VideoWidth" />
<return-property column="IsDeleted" name="IsDeleted" />
<return-property column="CreatedOn" name="CreatedOn" />
<return-property column="ModifiedOn" name="ModifiedOn" />
<return-property column="CreatedBy" name="CreatedBy" />
<return-property column="ModifiedBy" name="ModifiedBy" />
<return-property column="CreatedByName" name="CreatedByName" />
<return-property column="ModifiedByName" name="ModifiedByName" />
</return>
exec dbo.gbi_sp_GetRelatedResources :pageSize, :pageIndex, :resourceId
</sql-query>
And my class:
public class Resource : DomainEntity
{
[Required(ErrorMessage = "Please enter a name"), StringLength(100, ErrorMessage = "Name length can not exceed 100 characters")]
public virtual string Name { get; set; }
[StringLength(200, ErrorMessage = "Description length can not exceed 200 characters")]
public virtual string Description { get; set; }
public virtual string Filename { get; set; }
public virtual string Filetype { get; set; }
public virtual bool IsPublic { get; set; }
public virtual bool IsFeatured { get; set; }
[StringLength(500, ErrorMessage = "Embed Code length can not exceed 500 characters")]
public virtual string VideoEmbedCode { get; set; }
public virtual long? VideoId { get; set; }
public virtual long? VideoPlayerId { get; set; }
[StringLength(100, ErrorMessage = "Player Key length can not exceed 100 characters")]
public virtual string VideoPlayerKey { get; set; }
public virtual int? VideoHeight { get; set; }
public virtual int? VideoWidth { get; set; }
//public virtual int Rank { get; set; }
public virtual string Format {
get
{
if (ResourceFileType == ResourceFileType.EmbeddedVideo || ResourceFileType == ResourceFileType.VideoPlayer)
return "Video";
switch (Filetype)
{
case "pdf":
return "Adobe Acrobat";
case "docx":
case "doc":
return "Microsoft Word";
case "ppt":
case "pptx":
return "Microsoft PowerPoint";
case "xls":
case "xlsx":
return "Microsoft Excel";
default:
return Filetype.ToUpper();
}
}
}
public virtual ResourceFileType ResourceFileType
{
get
{
if (VideoId.HasValue)
return ResourceFileType.VideoPlayer;
if (!VideoEmbedCode.IsNullOrEmpty() || VideoId.HasValue)
return ResourceFileType.EmbeddedVideo;
return ResourceFileType.Document;
}
}
public virtual IEnumerable<Market> Markets { get; set; }
public virtual IEnumerable<Workstream> Workstreams { get; set; }
public virtual IEnumerable<Tag> Tags { get; set; }
public virtual IEnumerable<Topic> Topics { get; set; }
public virtual IEnumerable<ResourceType> Types { get; set; }
public override string ToString()
{
return Id + " - " + Name;
}
}
Ideally I'd like to just make a class like this to extend Resource
public class ResourceRanked : Resource
{
public virtual int Rank { get; set; }
}
But I can't get that to work unless I duplicate the xml mapping of the Resource class. I've looked at subclasses for nhibernate, but they don't seem to fit what I'm trying to do.
I couldn't find any suitable solution that worked for me, so I unfortunately just had to map another class specifically for the return result of the stored proc.
The better way to do this is to create a view on the tables from which the joined resultset is required and query that view in the stored procedure and map that view in the .hbm file . You can check the detail at the link http://sachinpachori10.blogspot.in/2013/06/nhibernate-returning-extra-properties.html
I have a qouestion regarding the AutoMap xml generation.
I have two classes:
public class User
{
virtual public Guid Id { get; private set; }
virtual public String Name { get; set; }
virtual public String Email { get; set; }
virtual public String Password { get; set; }
virtual public IList<OpenID> OpenIDs { get; set; }
}
public class OpenID
{
virtual public Guid Id { get; private set; }
virtual public String Provider { get; set; }
virtual public String Ticket { get; set; }
virtual public User User { get; set; }
}
The generated sequences of xml files are:
For User class:
<bag name="OpenIDs">
<key>
<column name="User_Id" />
</key>
<one-to-many class="BL_DAL.Entities.OpenID, BL_DAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
For OpenID class:
<many-to-one class="BL_DAL.Entities.User, BL_DAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="User">
<column name="User_id" />
</many-to-one>
I don't see the inverse=true attribute for the User mapping. Is it a normal behavior, or I made a mistake somewhere?
The default convention is not to add the inverse attribute. You will have to overwrite the convention to change that.
I am trying to map Users to each other. The senario is that users can have buddies, so it links to itself
I was thinking of this
public class User
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
public virtual DateTime? DateCreated { get; set; }
**public virtual IList<User> Friends { get; set; }**
public virtual bool Deleted { get; set; }
}
But am strugling to do the xml mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyVerse.Domain"
namespace="MyVerse.Domain" >
<class name="User" table="[User]">
<id name="Id">
<generator class="guid" />
</id>
<property name="FirstName" />
<property name="LastName" />
<property name="EmailAddress" />
<property name="Password" />
<property name="DateCreated" />
<property name="Deleted" />
<set name="Friends" table="UserFriend">
<key foreign-key="Id"></key>
<many-to-many class="User"></many-to-many>
</set>
</class>
</hibernate-mapping>
something like
<bag name="Friends" table="assoc_user_table" inverse="true" lazy="true" cascade="all">
<key column="friend_id" />
<many-to-many class="User,user_table" column="user_id" />
</bag>
Consider using the repository pattern. Create a Repository contract and a base abstract class that takes one of your entities as a type (your mapped class)
Open the session when the repository is initialized and close when destroyed. (implement IDisposable).
Then make sure all of your access to the session happens within the using statement:
[pseudo-code]:
using(var repository = RepositoryFactory<EntityType>.CreateRepository())
{
var entity = repository.get(EntityID);
foreach (somesubclass in entity.subclasscollection)
{
//Lazy loading can happen here, session is still open with the repository
... Do Something
}
}
I use a base abstract class for my Repositories. This one is for my readonly repository but you'll get the drift. They key is to keep your units of work small, open the session only when you have something to do with the database, then let it close on the dispose. Here's the base class, disclaimer YMMV:
public interface IEntity
{
int Id { get; set; }
}
public interface IRORepository<TEntity> : IDisposable where TEntity : IEntity
{
List<TEntity> GetAll();
TEntity Get(int id);
}
public abstract class RORepositoryBase<T> : IRORepository<T> where T : IEntity
{
protected ISession NHibernateSession;
protected RORepositoryBase()
{
NHibernateSession = HibernateFactory.OpenSession();
NHibernateSession.DefaultReadOnly = true;
}
public ISession Session { get { return NHibernateSession; } }
public void Dispose()
{
NHibernateSession.Flush();
NHibernateSession.Close();
NHibernateSession.Dispose();
}
public virtual List<T> GetAll()
{
return NHibernateSession.Query<T>().ToList();
}
public virtual T Get(int id)
{
return NHibernateSession.Get<T>(id);
}
}