Fluent NHibernate - How to map a List<IInterface> to multiple types? - fluent-nhibernate

I am trying to map a list containing instances of different types that all implements a common interface with Fluent NHibernate.
Below is a simplified example of how I want my model to look like. I want all types of questions to be stored in the same table and all types of answers to be stored in one table per type.
When using the Mapping in the example for survey Nhibernate treats all questions as IQuestion, and all Answers as IAnswer
What am I doing wrong?
public class SurveyMap : ClassMap<Survey>
{
public SurveyMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Questions).Cascade.All();
HasMany(x => x.Answers).Cascade.All();
}
}
public class BoolAnswerMap : SubclassMap<BoolAnswer>
{
public BoolAnswerMap()
{
Map(x => x.Value).Nullable();
References(x => x.Question);
}
}
public class DecimalAnswerMap : SubclassMap<DecimalAnswer>
{
public DecimalAnswerMap()
{
Map(x => x.Value).Nullable();
References(x => x.Question);
}
}
public class AnswerMap : ClassMap<IAnswer>
{
public AnswerMap()
{
Id(x => x.Id);
}
}
public class BoolQuestionMap : SubclassMap<BoolQuestion>
{
public BoolQuestionMap()
{
//HasMany(x => x.SubQuestions).Cascade.All(); -- Let's leave the subquestions for now
}
}
public class DecimalQuestionMap : SubclassMap<DecimalQuestion>
{
public DecimalQuestionMap()
{
}
}
public class QuestionMap : ClassMap<IQuestion>
{
public QuestionMap()
{
Id(x => x.Id);
Map(x => x.QuestionText).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
}
public class Survey{
private IList<IQuestion> questions = new List<IQuestion>();
private IList<IAnswer> answers = new List<IAnswer>();
public virtual string Name { get; set; }
public virtual IEnumerable<IQuestion> Questions { get { return questions; } }
public virtual IEnumerable<IAnswer> Answers { get { return answers; } }
public virtual void AddQuestion(IQuestion question){
questions.Add(question);
}
public virtual void AddAnswer(IAnswer answer{
answers.Add(answer);
}
}
public interface IQuestion{
int Id { get; set; };
string QuestionText { get; set; }
}
public interface IAnswer{
int Id { get; set; }
IQuestion Question { get; set; }
}
public class BoolQuestion: IQuestion{
private IList<IQuestion> subQuestions = new List<IQuestion>();
int Id { get; set; };
string QuestionText { get; set; }
public virtual IEnumerable<IQuestion> SubQuestions { get { return subQuestions; } }
public virtual void AddSubQuestion(IQuestion question){
subQuestions.Add(question);
}
}
//You could argue that this could be just Question (but this is a simplified example)
public class DecimalQuestion: IQuestion{
int Id { get; set; };
string QuestionText { get; set; }
}
public class BoolAnswer : IAnswer {
public int Id { get; set; }
public IQuestion Question { get; set; }
bool Value { get; set; }
}
public class DecimalAnswer : IAnswer {
public int Id { get; set; }
public IQuestion Question { get; set; }
decimal Value { get; set; }
}

ReferencesAny should do what you want.

Related

Fluent Nhibernate Composite Key mapping error

Composite Key mapping is not working in below scenario.
Database tables are as below.
Employee { Emp_ID, Name, Role_ID } (Role_ID is foreign key from Role table);
Leave { Leave_ID, Leave_Date, Leave_Comment};
Employee_Leave { Emp_ID, Leave_ID, Approval }; (EMP_ID and Leave_ID are composite key from Employee and Leave table respectively)
Entity classes are as below.
class Employee
{
public virtual string ID { get; set; }
public virtual string Name { get; set; }
public virtual Role EmpRole { get; set; }
}
public class Leave
{
virtual public Int16 LeaveID { get; set; }
virtual public String LeaveDate { get; set; }
virtual public String Comment { get; set; }
}
public class EmployeeLeaveApproval
{
public virtual string EMP_ID { get; set; }
public virtual int Leave_ID { get; set; }
public virtual string Approval { get; set; }
}
Mapping classes are as below.
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Table("Employee");
Id(x => x.ID, "ID");
Map(x => x.Name, "NAME");
References(x => x.EMPRole, "ROLE_ID").Not.LazyLoad();
}
}
public class LeaveMap : ClassMap<Leave>
{
public LeaveMap()
{
Table("Leave");
Id(x => x.LeaveID, "LEAVE_ID");
Map(x => x.LeaveDate, "LEAVE_DATE");
Map(x => x.Comment, "LEAVE_COMMENT");
}
}
Below class mapping is working fine.
public class EmployeeLeaveApprovalMap : ClassMap<EmployeeLeaveApproval>
{
public EmployeeLeaveApprovalMap()
{
Table("Employee_Leave");
Id(x => x.EMP_ID, "EMP_ID");
Map(x => x.Leave_ID, "LEAVE_ID");
Map(x => x.Approval, "Approval");
}
}
Below class mapping is not working.
public class EmployeeLeaveApprovalMap : ClassMap<EmployeeLeaveApproval>
{
public EmployeeLeaveApprovalMap()
{
Table("Employee_Leave");
CompositeId()
.KeyProperty(x => x.EMP_ID, "EMP_ID")
.KeyProperty(x => x.Leave_ID, "LEAVE_ID");
Map(x => x.Approval, "Approval");
}
}
Getting error "Database was not configured through Database method." while calling method BuildSessionFactory.
Many many thanks in advance for any help.
Found solution rather to say found the mistake i am doing.
Equal and GetHashCode methods are left being implemented in my code.
Below is the corrected entiity,
public class EmployeeLeaveApproval : Object
{
public virtual string EMP_ID { get; set; }
public virtual int Leave_ID { get; set; }
public virtual string Approval { get; set; }
public EmployeeLeaveApproval() {}
public override bool Equals(object obj)
{
if (obj == null)
return false;
EmployeeLeaveApproval EL = (EmployeeLeaveApproval)obj;
if (EL == null)
return false;
if (EMP_ID == EL.EMP_ID && Leave_ID == EL.Leave_ID)
return true;
return false;
}
public override int GetHashCode()
{
return (EMP_ID + "|" + Leave_ID).GetHashCode();
}
}
regards..Dharmendra

Fluent NHibernate - HasOne mapped to a ReferencesAny

I have the following POCO classes:
public class Container
{
public virtual Int64 ContainerId { get; protected set; }
public virtual string Name { get; set; }
public virtual Location Location { get; set; }
}
public abstract class Location
{
public virtual Int64 LocationId { get; protected set; }
public virtual string Name { get; set; }
}
public class UniqueLocation : Location
{
public virtual Container Container { get; set; }
}
public class SharedLocation : Location
{
public SharedLocation()
{
this.Containers = new List<Container>();
}
public virtual IList<Container> Containers { get; set; }
}
and the following Fluent mapping:
public class ContainerMap: ClassMap<Container>
{
public ContainerMap()
{
Table("Containers");
Id(x => x.ContainerId);
Map(x => x.Name);
ReferencesAny(x => x.Location).IdentityType<Int64>().EntityTypeColumn("LocationType").EntityIdentifierColumn("LocationId")
.AddMetaValue<UniqueLocation>("U")
.AddMetaValue<SharedLocation>("S");
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Table("Locations");
Id(x => x.LocationId);
Map(x => x.Name);
}
}
public class UniqueLocationMap : SubclassMap<UniqueLocation>
{
public UniqueLocationMap()
{
HasOne(x => x.Container).PropertyRef(x => x.Location).ForeignKey("LocationId").Cascade.All().Constrained();
}
}
public class SharedLocationMap : SubclassMap<SharedLocation>
{
public SharedLocationMap()
{
HasMany(x => x.Containers).KeyColumn("LocationId");
}
}
The problem is HasOne() mapping generates the following exception: "broken column mapping for: Container.Location of: UniqueLocation, type Object expects 2 columns, but 1 were mapped".
How do I tell HasOne() to use/map both LocationType and LocationId?
AFAIK Where conditions are not possible on Entity references except using Formulas. The design seems a strange because it would be nasty to change a unique Location to a shared location.
what you want can be done using:
Reference(x => x.Container).Formula("(SELECT c.Id FROM Container c WHERE c.LocationId = Id AND c.LocationType = 'U')");
But i would prefere
class Location
{
...
public virtual bool IsUnique { get { return Container.Count == 1; } }
}

NHibernate - Delete Not Peristing in the Database

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.

How am I supposed to query for a persisted object's property's subproperty in nhibernate?

I'm feeling dumb.
public class Uber
{
public Foo Foo { get; set; }
public Bar Bar { get; set; }
}
public class Foo
{
public string Name { get; set; }
}
...
var ubercharged = session.CreateCriteria(typeof(Uber))
.Add(Expression.Eq("Foo.Name", "somename"))
.UniqueResult<Uber>();
return ubercharged;
This throws a "could not resolve property" error.
What am I doing wrong? I want to query for an Uber object that has a property Foo which has a Name of "somename".
updated with real life example, repository call, using fluent nhibernate:
public UserPersonalization GetUserPersonalization(string username)
{
ISession session = _sessionSource.GetSession();
var personuser = session.CreateCriteria(typeof(UserPersonalization))
.Add(Expression.Eq("User.Username", username))
.UniqueResult<UserPersonalization>();
return personuser;
}
The classes/mappings:
public class User
{
public virtual Guid UserId { get; set; }
public virtual string Username { get; set; }
public virtual string Email { get; set; }
public virtual string PasswordHash { get; set; }
public virtual string PasswordSalt { get; set; }
public virtual bool IsLockedOut { get; set; }
public virtual bool IsApproved { get; set; }
}
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class UserPersonalization
{
public virtual int UserPersonalizationId { get; set; }
public virtual Person Person { get; set; }
public virtual User User { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserId).GeneratedBy.Guid().ColumnName("UserId");
Map(x => x.Username);
Map(x => x.PasswordHash);
Map(x => x.PasswordSalt);
Map(x => x.Email);
Map(x => x.IsApproved);
Map(x => x.IsLockedOut);
}
}
public class UserPersonalizationMap : ClassMap<UserPersonalization>
{
public UserPersonalizationMap()
{
WithTable("UserPersonalization");
Id(x => x.UserPersonalizationId).ColumnName("UserPersonalizationId");
References(x => x.Person).ColumnName("PersonId");
References(x => x.User).ColumnName("UserId");
}
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.PersonId).ColumnName("PersonId");
Map(x => x.Name);
References(x => x.Company).ColumnName("CompanyId");
}
}
Try this:
var ubercharged = session.CreateCriteria(typeof(Uber))
.CreateCriteria("Foo")
.Add(Restrictions.Eq("Name", "somename"))
.UniqueResult<Uber>();
Can you sort using "ubercharged.AddOrder(Order.asc("Foo.Name")) syntax? This syntax should work in NHib 2.01. If not, your maps are not working correctly.
Stuart's answer should work fine for you though.

NHibernate: Best way to deal with intermediary table using Fluent NHibernate?

How would you map the following in Fluent NHibernate?
See "18.3. Customer/Order/Product"
http://www.hibernate.org/hib_docs/nhibernate/html/example-mappings.html
The following solution uses the same approach as the solution in the example, and the generated XML is as good as the same. I have omitted specifying column names and such things for brevity.
Domain:
public class Customer
{
private ISet<Order> orders = new HashedSet<Order>();
public long Id { get; set; }
public string Name { get; set; }
public ISet<Order> Orders
{
get { return orders; }
private set { orders = value; }
}
}
public class Order
{
public long Id { get; set; }
public DateTime Date { get; set; }
public Customer Customer { get; set; }
public IList<LineItem> LineItems { get; private set; }
}
public class LineItem
{
public int Quantity { get; set; }
public Product Product { get; set; }
}
public class Product
{
public long Id { get; set; }
public string SerialNumber { get; set; }
}
Mapping:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.Name);
HasMany<Order>(x => x.Orders)
.IsInverse()
.AsSet();
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.Date);
References<Customer>(x => x.Customer);
HasMany<LineItem>(x => x.LineItems)
.Component(c =>
{
c.Map(x => x.Quantity);
c.References<Product>(x => x.Product);
}).AsList();
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.SerialNumber);
}
}
To see the generated XML mapping, you can use this code:
Configuration config = new Configuration().Configure();
PersistenceModel model = new PersistenceModel();
model.addMappingsFromAssembly(typeof(CustomerMap).Assembly);
model.Configure(config);
model.WriteMappingsTo("your folder name here");
I hope it helps.
/Erik