Fluent - Relationship to one child on Identity, relationship to other child on composite key - fluent-nhibernate

So, I have an Invoice object like so:
public class Invoice
{
public virtual long InvoiceId { get; set; }
public virtual string InvoiceNumber{ get; set; }
public virtual Customer Customer { get; set; }
public virtual Site Site { get; set; }
public virtual IList<InvoiceLineItem> LineItems { get; set; }
public virtual IList<InvoicePayment> Transactions { get; set; }
}
Then, I have an invoice line item like this
public class InvoiceLineItem
{
public virtual long InvoiceLineItemId { get; set; }
public virtual Invoice Invoice{ get; set; }
}
And finally, an invoice Payment
public class InvoicePayment
{
public virtual long InvoicePaymentId { get; set; }
public virtual Invoice Invoice{ get; set; }
}
The problem is this, in my underlying schema for InvoicePayment, I have InvoiceNumber, SiteId (to the Site object), and CustomerId (to the Customer object).
In InvoiceLineItem, I have InvoiceId linking back to Invoice.
So, my mapping for Invoice looks something like this:
public sealed class InvoiceMap : ClassMap<Invoice>
{
public InvoiceMap()
{
Table("InvoiceView");
Id(x => x.InvoiceId).GeneratedBy.Identity();
Map(x => x.InvoiceNumber);
References<Site>(x => x.Site, "SiteId");
References<Customer>(x => x.Customer, "CustomerId");
HasMany<InvoiceLineItem>(x => x.LineItems)
.Inverse();
HasMany<InvoicePayment>(x => x.Transactions)
.KeyColumns.Add("SiteId")
.KeyColumns.Add("EPayCustomerId")
.KeyColumns.Add("InvoiceNumber")
.Inverse();
}
}
Line Items mapping
public class InvoiceLineItemMap : ClassMap<InvoiceLineItem>
{
public InvoiceLineItemMap()
{
Table("InvoiceLineItems");
Id(x => x.InvoiceLineItemId).GeneratedBy.Identity();
References<FTNI.Core.Model.Invoice.Invoice>(x => x.Invoice, "InvoiceId");
}
}
And finally my Invoice payments mapping
public class InvoicePaymentMap : ClassMap<InvoicePayment>
{
public InvoicePaymentMap()
{
Table("InvoicePayments");
Id(x => x.InvoicePaymentId).GeneratedBy.Identity();
CompositeId()
.KeyProperty(x => x.Site, "SiteId")
.KeyProperty(x => x.Customer, "CustomerId")
.KeyProperty(x => x.InvoiceNumber);
References<Site>(x => x.Site, "SiteId");
References<EPayCustomer>(x => x.Customer, "CustomerId");
References<FTNI.Core.Model.Invoice.Invoice>(x => x.Invoice)
.Columns("SiteId", "CustomerId", "InvoiceNumber")
.Nullable();
}
}
So, as it is, I am getting an error
Foreign key (FKE9F746C567E71B3F:InvoiceLineItems [InvoiceId])) must have same number of columns as the referenced primary key (InvoiceView [SiteId, CustomerId, InvoiceNumber])
How can I adjust my mappings so I join to the invoice payments on a composite id and the line items on the identity column?

the error message looks a bit weird. I would expect a CompositeId()... in InvoiceMap to produce this error.
The error stems from the fact that each entity can only have 1 Id and the call to CompositeId overrides the previous call to Id, hence the Id of Invoice is the CompositeId and a single Column in InvoiceLineItem doesnt match.
try the following:
public class Invoice
{
public virtual long InvoiceId { get; set; }
public virtual InvoiceIdentity Identity { get; set; }
public virtual IList<InvoiceLineItem> LineItems { get; set; }
public virtual IList<InvoicePayment> Transactions { get; set; }
}
public class InvoiceIdentity
{
public virtual string InvoiceNumber{ get; private set; }
public virtual Customer Customer { get; private set; }
public virtual Site Site { get; private set; }
public Identity(string invoicenumber, Customer customer, Site site)
{
InvoiceNumber = invoicenumber;
Customer = customer;
Site = site;
}
}
public sealed class InvoiceMap : ClassMap<Invoice>
{
public InvoiceMap()
{
Table("InvoiceView");
Id(x => x.InvoiceId).GeneratedBy.Identity();
Component(x => x.Identity, c =>
{
c.Map(x => x.InvoiceNumber);
c.References(x => x.Site, "SiteId");
c.References(x => x.Customer, "CustomerId");
});
HasMany(x => x.LineItems)
.Inverse();
HasMany(x => x.Transactions)
.PropertyRef(i => i.Identity)
.KeyColumns.Add("SiteId", "EPayCustomerId", "InvoiceNumber")
.Inverse();
}
}
public class InvoicePaymentMap : ClassMap<InvoicePayment>
{
public InvoicePaymentMap()
{
Table("InvoicePayments");
Id(x => x.InvoicePaymentId).GeneratedBy.Identity();
References(x => x.Invoice)
.Columns("SiteId", "CustomerId", "InvoiceNumber")
.PropertyRef(i => i.Identity)
.Nullable();
}
}

Related

Many to Many Nhibernate - Duplicate records and no insert

I have three tables in a many to many relationship with the nhibernate maps below. My objects are also below. A portfolio item can have many tags. The problem I am having is
1) update save another tag even when the name is the same as last time. So duplicate records get inserted into tag when the tag is the same. So for example if the tag for one portfolio object was abc the next portfolio item that adds the tag should reference this record rather than reinserting abc. I think this is because of the id column in the tag map. Nhibernate needs an id though.
2) Create does not add records in the join table. Records in the join table are only added on updates.
Domain Objects
public class Portfolio {
public Portfolio() {
PortfolioImage = new List<Portfolioimage>();
Tag = new List<Tag>();
}
public virtual int PortfolioId { get; set; }
public virtual string AliasTitle { get; set; }
public virtual string MetaDescription { get; set; }
public virtual string Title { get; set; }
public virtual string Client { get; set; }
public virtual string Summary { get; set; }
public virtual string Url { get; set; }
public virtual string MainImage { get; set; }
public virtual string TitleAlt { get; set; }
public virtual string Description { get; set; }
public virtual IList<Portfolioimage> PortfolioImage { get; set; }
public virtual IList<Tag> Tag { get; set; }
}
public class Portfoliotag {
public virtual int Id { get; set; }
public virtual Portfolio Portfolio { get; set; }
public virtual Tag Tag { get; set; }
}
public class Tag {
public Tag() {
Portfolio = new List<Portfolio>();
}
public virtual int TagId { get; set; }
public virtual string TagVal { get; set; }
public virtual IList<Portfolio> Portfolio { get; set; }
}
Maps
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.PortfolioId).GeneratedBy.Identity().Column("PortfolioId");
Map(x => x.AliasTitle).Column("AliasTitle").Not.Nullable();
Map(x => x.MetaDescription).Column("MetaDescription").Not.Nullable();
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Client).Column("Client").Not.Nullable();
Map(x => x.Summary).Column("Summary").Not.Nullable();
Map(x => x.Url).Column("Url");
Map(x => x.MainImage).Column("MainImage");
Map(x => x.TitleAlt).Column("TitleAlt");
Map(x => x.Description).Column("Description").Not.Nullable();
HasMany(x => x.PortfolioImage).KeyColumn("PortfolioId").Inverse();
HasManyToMany(x => x.Tag).Table("PortfolioTag").ParentKeyColumn("PortfolioId").ChildKeyColumn("TagId").LazyLoad().Cascade.All().Fetch.Join();
}
}
public class PortfoliotagMap : ClassMap<Portfoliotag> {
public PortfoliotagMap() {
Table("PortfolioTag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Portfolio).Not.Nullable().Cascade.SaveUpdate().Column("PortfolioId");
References(x => x.Tag).Not.Nullable().Cascade.SaveUpdate().Column("TagId");
}
}
public class TagMap : ClassMap<Tag> {
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.TagId).GeneratedBy.Identity().Column("TagId");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
//HasMany(x => x.PortfolioTag).KeyColumn("TagId");
// HasMany(x => x.PortfolioTag).Cascade.AllDeleteOrphan().Inverse().Fetch.Join().KeyColumn("TagId");
HasManyToMany(x => x.Portfolio).Table("PortfolioTag").ParentKeyColumn("PortfolioId").ChildKeyColumn("TagId").LazyLoad().Inverse().Cascade.AllDeleteOrphan();
}
}
given the classes
public class Portfolio
{
public Portfolio()
{
Tag = new List<Tag>();
}
public virtual int PortfolioId { get; protected set; }
public virtual IList<Tag> Tag { get; protected set; }
}
public class Tag
{
public virtual int TagId { get; set; }
public virtual string Name { get; set; }
}
the this mapping should suffice
public class PortfolioMap : ClassMap<Portfolio>
{
public PortfolioMap()
{
Id(x => x.PortfolioId).GeneratedBy.Identity().Column("PortfolioId");
HasManyToMany(x => x.Tag)
.Table("PortfolioTag")
.ParentKeyColumn("PortfolioId")
.ChildKeyColumn("TagId")
.Cascade.All()
.Fetch.Join();
}
}
assigning existing tags has to be handled in code which can cache or query involved tags more efficiently than automatic querying by any framework.
Update:
example usage which works for me
public void Save(int portfolioId, IEnumerable<string> tagnames)
{
using (var tx = session.BeginTransaction())
{
var tags = Session.QueryOver<Tag>().WhereProperty(x => x.Name).In(tagnames).List();
var portfolio = session.Get<Portfolio>(portfolioId);
portfolio.Tags.Clear();
portfolio.Tags.AddRange(tags);
tx.Commit();
}
}

Nhibernate databinding foreign key to lookupedit

I would like to databind the foreign key property Product.CategoryId to a Devexpess Lookupedit in Windows Forms Application.
So
lookEditCategory.DataBindings
.Add(new Binding("EditValue", Product, "CategoryId ", true,
DataSourceUpdateMode.OnPropertyChanged));
lookEditCategory.Properties.Columns.Clear();
lookEditCategory.Properties.NullText = "";
lookEditCategory.Properties.DataSource = CatCol;
lookEditCategory.Properties.ValueMember = "CategoryId";
lookEditCategory.Properties.DisplayMember = "CategoryName";
var col = new LookUpColumnInfo("CategoryName") { Caption = "Type" };
lookEditCategory.Properties.Columns.Add(col);
The problem is that Nhibernate does not expose the foreign key Product.CategoryId. Instead my entity and mapping are like this
public partial class Product
{
public virtual int ProductId { get; set; }
[NotNull]
[Length(Max=40)]
public virtual string ProductName { get; set; }
public virtual bool Discontinued { get; set; }
public virtual System.Nullable<int> SupplierId { get; set; }
[Length(Max=20)]
public virtual string QuantityPerUnit { get; set; }
public virtual System.Nullable<decimal> UnitPrice { get; set; }
public virtual System.Nullable<short> UnitsInStock { get; set; }
public virtual System.Nullable<short> UnitsOnOrder { get; set; }
public virtual System.Nullable<short> ReorderLevel { get; set; }
private IList<OrderDetail> _orderDetails = new List<OrderDetail>();
public virtual IList<OrderDetail> OrderDetails
{
get { return _orderDetails; }
set { _orderDetails = value; }
}
public virtual Category Category { get; set; }
public class ProductMap : FluentNHibernate.Mapping.ClassMap<Product>
{
public ProductMap()
{
Table("`Products`");
Id(x => x.ProductId, "`ProductID`")
.GeneratedBy
.Identity();
Map(x => x.ProductName, "`ProductName`")
;
Map(x => x.Discontinued, "`Discontinued`")
;
Map(x => x.SupplierId, "`SupplierID`")
;
Map(x => x.QuantityPerUnit, "`QuantityPerUnit`")
;
Map(x => x.UnitPrice, "`UnitPrice`")
;
Map(x => x.UnitsInStock, "`UnitsInStock`")
;
Map(x => x.UnitsOnOrder, "`UnitsOnOrder`")
;
Map(x => x.ReorderLevel, "`ReorderLevel`")
;
HasMany(x => x.OrderDetails)
.KeyColumn("`ProductID`")
.AsBag()
.Inverse()
.Cascade.None()
;
References(x => x.Category)
.Column("`CategoryID`");
}
}
}
I cannot add the property CategoryID in my Product entity and mapping because then it will be mapped twice.
Is there any solution?
Yes. Do NOT use your domain entities in the UI.
Sometimes your UI doesn't need (and shouldn't be aware of) all the properties of your domain objects.
Other times, it needs DTOs that contain data from different domain sources (for example- a list of CourseNames for the Student screen), or, like in your case- it needs the data to be represented in a slightly different way.
So the best way would be to create your DTOs with all (and only) the properties needed by the UI.
See this SO question for further details.

two class reference eachother , how to do Not.LazyLoad in fluent nhibernate

i have two class
public class ProInfo
{
public ProInfo()
{
ProPrices = new List<ProPrice>();
}
public virtual Guid ProID { get; set; }
public virtual string ProCate { get; set; }
public virtual string Name { get; set; }
public virtual string Unit { get; set; }
public virtual string PicName { get; set; }
public virtual string Memo { get; set; }
public virtual bool DeleteFlag { get; set; }
public virtual DateTime LastUpDateTime { get; set; }
public virtual IList<ProPrice> ProPrices { get; set; }
}
public class ProPrice
{
public virtual int Id { get; set; }
public virtual AreaInfo AreaInfo { get; set; }
public virtual ProInfo ProInfo { get; set; }
public virtual Decimal Price { get; set; }
}
mapping codes are :
public ProInfoMap()
{
Id(x => x.ProID);
Map(x => x.DeleteFlag);
Map(x => x.Name);
Map(x => x.PicName);
Map(x => x.ProCate);
Map(x => x.Unit);
Map(x => x.LastUpDateTime);
HasMany<ProPrice>(x => x.ProPrices);
}
public ProPriceMap()
{
Id(x => x.Id);
Map(x => x.Price);
References<ProInfo> (x => x.ProInfo);
References<AreaInfo>(x => x.AreaInfo);
}
what i want is to disable the proprices's lazyload(), so i can get all the prices for the product from database. but, when i change the onetomany to this: HasMany<ProPrice>(x => x.ProPrices).Not.LazyLoad(), it cause an Infinite loop. what do i miss?
I don't know, where exactly the loop comes from, but your bidirectional association may cause this. You should declare one side as Inverse(). This can only be done in ProInfoMap, because it is a one-to-many relationship with a bidirectional association:
HasMany<ProPrice>(x => x.ProPrices).Inverse().Not.LazyLoad();
Try that. It may remove the infinite loop.

NHibernate / Fluent NHibernate Mapping

Is it possible to map the following situation?
A product class (currently a table)
An account class (currently a table)
An accountproduct class (currently a join table but with additional information related to a specific product and account)
What I'd ideally like is accountproduct to extend product and to be available from account as a property Products.
The product class would exist seperately and provide it's own peristance.
How about the following:
public class AccountProduct
{
public virtual int Id { get; set; }
public virtual DateTime Date { get; set; }
public virtual string Comments { get; set; }
public virtual Account Account { get; set; }
public virtual Product Product { get; set; }
public class AccountProductMap : ClassMap<AccountProduct>
{
public AccountProductMap()
{
Id(x => x.Id);
Map(x => x.Date);
Map(x => x.Comments);
References(x => x.Account);
References(x => x.Product);
}
}
}
public class Product
{
public virtual int Id { get; set; }
public virtual int Name { get; set; }
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
}
public class Account
{
public virtual int Id { get; set; }
public virtual int Name { get; set; }
public class AccountMap : ClassMap<Account>
{
public AccountMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
}

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