FluentNHibernate subclass and hierarchical data - fluent-nhibernate

I have an Account table that stores Master Accounts and Sub Accounts. Master Accounts are basically the same as Sub Accounts except that they can have an associated Company. Account is an abstract class and both MasterAccount and SubAccount derive from it.
A MasterAccount is any account entry with a null ParentAccountId. If an Account record has a ParentAccountId then it is a SubAccount and the ParentAccountId references the AccountId field for the MasterAccount.
I am trying get FluentNhibernate mappings for them.
The classes look like the following
public class Account : EntityBase
{
public Account() { }
public virtual string AccountNumber { get; set; }
public virtual string AccountName { get; set; }
public virtual string ContactRole { get; set; }
public virtual bool EmailBillDataFile { get; set; }
public virtual bool EmailBill { get; set; }
public virtual bool PostBill { get; set; }
public virtual BillingMethod BillingMethod { get; set; }
public virtual BillingAddressType BillingAddressType { get; set; }
public virtual Contact Contact { get; set; }
public virtual bool IsInvoiceRoot { get; set; }
public virtual string Password { get; set; }
public virtual bool HasRequestedInvoicing { get; set; }
public virtual bool IsInternational { get; set; }
public virtual decimal AmountPaid { get; set; }
public virtual decimal PreviousBill { get; set; }
public virtual void MakePayment(decimal amount)
{
MakePayment(amount, null);
}
public virtual void MakePayment(decimal amount, string invoiceNumber)
{
AmountPaid += amount;
if (string.IsNullOrEmpty(invoiceNumber))
LogActivity(string.Format("Made payment of {0:c}", amount));
else {
LogActivity(string.Format("Made payment of {0:c} on Invoice '{1}'", amount, invoiceNumber));
}
}
public virtual Invoice CreateInvoice()
{
Invoice invoice;
invoice = IsInternational ? new NoGstInvoice() : new Invoice();
// Can update invoice properties that rely on account data here.
return invoice;
}
#region Business Rules
public override IEnumerable<RuleViolation> GetRuleViolations()
{
if (string.IsNullOrEmpty(AccountName))
yield return new RuleViolation("Account Name required", "AccountName");
if (string.IsNullOrEmpty(AccountNumber))
yield return new RuleViolation("Acocunt Number required", "AccountNumber");
if (string.IsNullOrEmpty(Password))
yield return new RuleViolation("Password required", "Password");
yield break;
}
#endregion
}
public class MasterAccount : Account
{
private Company _company;
private IList<SubAccount> _subAccounts;
public MasterAccount() : this(null) { }
public MasterAccount(Company company)
{
_company = company;
_subAccounts = new List<SubAccount>();
}
public virtual Company Company
{
get { return _company; }
}
public virtual IEnumerable<SubAccount> SubAccounts
{
get { return _subAccounts; }
}
public virtual SubAccount CreateSubAccount(string accountNumber, string accountName)
{
var subAccount = new SubAccount(this)
{
AccountName = accountName,
AccountNumber = accountNumber,
Contact = this.Contact,
ContactRole = this.ContactRole,
PreviousBill = 0,
AmountPaid = 0,
BillingAddressType = this.BillingAddressType,
BillingMethod = this.BillingMethod,
IsInternational = this.IsInternational,
IsInvoiceRoot = false,
EmailBill = this.EmailBill,
EmailBillDataFile = this.EmailBillDataFile,
Password = this.Password,
PostBill = this.PostBill
};
return subAccount;
}
}
public class SubAccount : Account
{
private MasterAccount _masterAccount;
public SubAccount() { }
public SubAccount(MasterAccount master)
{
_masterAccount = master;
}
public virtual MasterAccount MasterAccount
{
get { return _masterAccount; }
}
}
The mappings I have are:
public class AccountMap : ClassMap<Account>
{
public AccountMap()
{
Table("Account");
Id(x => x.Id).Column("AccountId").GeneratedBy.Identity();
Map(x => x.AccountName).Length(50).Not.Nullable();
Map(x => x.AccountNumber).Length(10).Not.Nullable();
Map(x => x.ContactRole).Length(50);
Map(x => x.BillingMethod).Not.Nullable();
Map(x => x.EmailBill).Not.Nullable();
Map(x => x.PostBill).Not.Nullable();
Map(x => x.EmailBillDataFile).Not.Nullable();
Map(x => x.BillingAddressType).Not.Nullable();
Map(x => x.IsInvoiceRoot).Not.Nullable();
Map(x => x.HasRequestedInvoicing).Not.Nullable();
Map(x => x.IsInternational).Not.Nullable();
Map(x => x.PreviousBill).Not.Nullable();
Map(x => x.AmountPaid).Not.Nullable();
Map(x => x.Password).Length(20).Not.Nullable();
References(x => x.Contact).Column("ContactId").Not.Nullable();
DiscriminateSubClassesOnColumn("ParentAccountId");
}
}
public class MasterAccountMap : SubclassMap<MasterAccount>
{
public MasterAccountMap()
{
References(x => x.Company).Column("CompanyId");
HasMany(x => x.SubAccounts).KeyColumn("ParentAccountId").Inverse().Cascade.All();
}
}
public class SubAccountMap : SubclassMap<SubAccount>
{
public SubAccountMap()
{
References(x => x.MasterAccount).Column("ParentAccountId").Not.Nullable();
}
}
However, when I execute the following test:
[Test]
public void Can_add_subAccount_to_database()
{
var master = Session.Get<MasterAccount>(1);
var subAccount = master.CreateSubAccount("TST123", "Test Account");
Session.Save(subAccount);
Session.Flush();
Session.Clear();
var fromDb = Session.Get<SubAccount>(subAccount.Id);
Assert.AreNotSame(subAccount, fromDb);
}
I get an exception on the Session.Save(subAccount); line.
System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
I do not get the exception if I comment out the References mapping in SubAccountMap.
Any help on correctly mapping this relationship is appreciated.

Seems I needed to use the Formula method off DiscriminateSubClassesOnColumn
DiscriminateSubClassesOnColumn("").Formula("case when parentaccountid is null then '0' else '1' end");
and then use the following in each of the subclasses
DiscriminatorValue("0"); // In MasterAccountMap
DiscriminatorValue("1"); // in SubAccountMap
see http://wiki.fluentnhibernate.org/Fluent_mapping

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 automapping table-per-abstract-hierarchy / table-per-concrete-subclass

I have classes
public abstract class Content : IContent
{
public virtual Guid Id { get; protected set; }
public virtual IPage Parent { get; set; }
public virtual DateTime Created { get; set; }
/* ... */
}
public abstract class Page : Content, IPage
{
public virtual string Slug { get; set; }
public virtual string Path { get; set; }
public virtual string Title { get; set; }
/* ... */
}
public class Foo : Page, ITaggable
{
// this is unique property
// map to joined table
public virtual string Bar { get; set; }
// this is a unique collection
public virtual ISet<Page> Related { get; set; }
// this is "shared" property (from ITaggable)
// map to shared table
public virtual ISet<Tag> Tags { get; set; }
}
And as a result I'd like to have the following tables. I've tried implementing tons of different IConventions, but even the hierarchy mappings (table-per-abstract-hierarchy / table-per-concrete-subclass) seem to fail.
Content
Id
Type (discriminator)
ParentId
Created
Slug
Path
Title
Content_Tags (Tags from ITaggable)
ContentId
TagId
Content$Foo
Bar
Content$Foo_Related
ParentFooId
ChildPageId
I already have ugly, working fluent mappings, but I would like to get rid of some ugliness
public class ContentMapping : ClassMap<Content>
{
public ContentMapping()
{
Table("Content");
Id(x => x.Id).GeneratedBy.GuidComb();
References<Page>(x => x.Parent, "ParentId");
Map(x => x.Created);
DiscriminateSubClassesOnColumn("Type");
}
}
public class PageMapping : SubclassMap<Page>
{
public PageMapping()
{
Map(x => x.Slug);
Map(x => x.Path);
Map(x => x.Title);
}
}
public class ConcreteContentMapping<T> : SubclassMap<T> where T : Content, new()
{
public ConcreteContentMapping() : this(true) { }
protected ConcreteContentMapping(bool mapJoinTable)
{
DiscriminatorValue(typeof(T).FullName);
MapCommonProperties();
if(mapJoinTable)
{
MapJoinTableWithProperties(CreateDefaultJoinTableName(), GetPropertiesNotFrom(GetContentTypesAndInterfaces().ToArray()));
}
}
private void MapCommonProperties()
{
if (typeof(ITagContext).IsAssignableFrom(typeof(T)))
{
Map(x => ((ITagContext)x).TagDirectory);
}
if (typeof(ITaggable).IsAssignableFrom(typeof(T)))
{
HasManyToMany(x => ((ITaggable)x).Tags).Table("Content_Tags").ParentKeyColumn("ContentId").ChildKeyColumn("TagId").Cascade.SaveUpdate();
}
}
/* ... */
// something I would like to get rid of with automappings...
protected void MapCollectionProperty(JoinPart<T> table, PropertyInfo p)
{
var tableName = ((IJoinMappingProvider)table).GetJoinMapping().TableName + "_" + p.Name;
var elementType = p.PropertyType.GetGenericArguments()[0];
var method = table.GetType().GetMethods().Where(m => m.Name == "HasManyToMany")
.Select(m => new { M = m, P = m.GetParameters() })
.Where(x => x.P[0].ParameterType.GetGenericArguments()[0].GetGenericArguments()[1] == typeof(object))
.FirstOrDefault().M.MakeGenericMethod(elementType);
dynamic m2m = method.Invoke(table, new object[] { MakePropertyAccessExpression(p)});
m2m.Table(tableName).ParentKeyColumn("Parent" + typeof(T).Name + "Id").ChildKeyColumn("Child" + elementType.Name + "Id");
}
protected Expression<Func<T, object>> MakePropertyAccessExpression(PropertyInfo property)
{
var param = Expression.Parameter(property.DeclaringType, "x");
var ma = Expression.MakeMemberAccess(param, property);
return Expression.Lambda<Func<T, object>>(ma, param);
}
}
How do I get the same result with automappings?

Illegal access to loading collection in Fluent and Nhibernate

I'm not able to understand why the problem is occurring. Please let me know if there are any errors, I am very new to this topic.
public class Department
{
public virtual int Dept_id { get; set; }
public virtual String Dept_name { get; set; }
public virtual IList<Student> Students { get; set; }
//public virtual ICollection<Student> Students { get; set; }
public Department()
{
Students = new List<Student>();
}
}
public class Student
{
public Student()
{
}
//private int _Dept_id;
//public virtual Guid StudentId { get; set; }
public virtual Guid StudentId { get; set; }
/*public virtual int Dept_id
{
get { return this._Dept_id; }
set { this._Dept_id = value; }
}*/
public virtual int Dept_id { get; set; }
public virtual String Name { get; set; }
public virtual int Age { get; set; }
public virtual String Address { get; set; }
public virtual Department Department { get; set; }
}
public class DepartmentMap : ClassMap<Department>
{
public DepartmentMap()
{
Table("Department");
Id(x => x.Dept_id).Column("Dept_id");
Map(x => x.Dept_name).Column("Dept_name");
HasMany(x => x.Students).KeyColumn("Student_id").Inverse()
.Cascade.All();
}
}
public class StudentMap :ClassMap<Student>
{
public StudentMap()
{
Table("Student");
Id(x => x.StudentId).Column("Student_id").GeneratedBy.GuidComb();
Map(x => x.Name);
Map(x => x.Age);
Map(x => x.Address);
References(x => x.Department).Column("Dept_id")
.Not.Nullable().Not.LazyLoad();
}
}
Now when I am trying this code
[WebMethod(EnableSession = true)]
public List<Student> Students()
{
IList<Student> student = new List<Student>();
ISession session = NHibernateHelper.OpenSession();
student = session.Query<Student>().ToList();
return student.ToList();
}
it gives error in loading the students list inside the department as
illegal access to loading collection
What is lacking in this code and why this is happening?
sorry my bad !! there are cetain changes i made which made it working .. though not sure of apparent shortcomings of the mentioned idea below
changed student class as :
public class Student
{
public Student()
{
}
public virtual Guid StudentId { get; set; }
public virtual int Dept_id
{
get { return Department.Dept_id; }
set { this.Dept_id = Department.Dept_id; }
}
public virtual String Name { get; set; }
public virtual int Age { get; set; }
public virtual String Address { get; set; }
public virtual Department Department { get; set; }
}
and student mapping for the reference as
References(x => x.Department).Column("Dept_id").Cascade.All();
Note : there should be no single Dept Id mapping
and changed the DepartmentMap as :
public DepartmentMap()
{
Table("Department");
Id(x => x.Dept_id).Column("Dept_id");
Map(x => x.Dept_name).Column("Dept_name");
HasMany(x => x.Students).KeyColumn("Dept_id").AsBag();
}

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.

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