References in fluent hibernate mapping seems dosnt work correctly - nhibernate

I'm using fluent hibernate 1.2.0.712 and nhibernate 3.2.0.4000 in my mvc project as an OR mapper, the problem is :
this is my oJob object:
public class Job{
virtual public Enquiry Enquiry { get; set; }
virtual public long Id { get; set; }
}
and this is Enquiry:
public class Enquiry {
virtual public long Id { get; set; }
}
and here is JobMap:
public class JobMap: ClassMap<Job>
{
public JobMap()
{
Schema("dbo");
Id(p => p.Id)
.Column("Id");
References(p => p.Enquiry);
}
}
i expect that each job has exactly one enquiry
but sometimes that i check sql server i see there are some records in job table with different ids that all have the same enquiryid
and i checked it many times and dont know when exactly it happens, what is the problem?

You must change your mapping : change
References(p => p.Enquiry);
by
HasOne(p => p.Enquiry);

Related

NHibernate exception: could not initialize a collection, Invalid column name. Fluent mapping. Maybe a many-to-one issue?

I am puzzled and frustrated by an exception I'm getting via NHibernate. I apologize for the length of this post, but I've tried to include an appropriate level of detail to explain the issue well enough to get some help!
Here's the facts:
I have a Person class which contains a property BillingManager, which is also a Person type. I map this as an FNH "Reference".
I have an ExpenseReport class which contains a property SubmittedBy, which is a Person type. I map this as an FNH "Reference".
I have a BillableTime class which contains a property Person, which is a Person type. I map this as an FNH "Reference".
Person contains a collection (IList) of ExpenseReport types (property ExpenseReports)
Person contains a collection (IList) of BilledTime types (property Time)
(See classes and mappings at bottom of post.)
All was cool until I added the IList<BilledTime> Time collection to Person. Now, when I try to access _person.Time, I get an exception:
The code:
// Get billable hours
if (_person.Time == null ||
_person.Time.Count(x => x.Project.ProjectId == project.ProjectId) == 0)
{
// No billable time for this project
billableHours = Enumerable.Repeat(0F, 14).ToArray();
}
The exception:
could not initialize a collection:
[MyApp.Business.Person.Time#211d3567-6e20-4220-a15c-74f8784fe47a]
[SQL: SELECT
time0_.BillingManager_id as BillingM8_1_,
time0_.Id as Id1_,
time0_.Id as Id1_0_,
time0_.ReadOnly as ReadOnly1_0_,
time0_.DailyHours as DailyHours1_0_,
time0_.Week_id as Week4_1_0_,
time0_.Person_id as Person5_1_0_,
time0_.Project_id as Project6_1_0_,
time0_.Invoice_id as Invoice7_1_0_
FROM [BillableTime] time0_
WHERE time0_.BillingManager_id=?]
It's true that BillingManager_id is an invalid column name, it doesn't exist in the BillableTime table. However, I don't understand why NHB has created this SQL... doesn't make sense to me. I have seen this "Invalid column name" exception a lot when searching for a solution, but nothing seems to work. Even more confusing: like BilledTime, the ExpenseReport type also contains a reference to Person and it works perfectly.
One thing I was able to figure out is that if I remove the BillingManager reference from the Person mapping (References(p => p.BillingManager)), the exception goes away and things seem to work (with respect to BillableTime; it of course breaks the BillingManager persistence). Now it seems like there is some "self-reference" problem, since the Person.BillingManager property is itself a reference to a Person.
Any idea what is going on here? I'm at a loss...
Thanks.
=== Classes & Mappings ===
public class Person
{
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual Person BillingManager { get; set; }
public virtual IList<ExpenseReport> ExpenseReports { get; set; }
public virtual IList<BillableTime> Time { get; set; }
}
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(p => p.UserId).GeneratedBy.Assigned();
Map(p => p.LastName).Not.Nullable();
Map(p => p.FirstName).Not.Nullable();
References(p => p.BillingManager);
HasMany(p => p.ExpenseReports).Cascade.AllDeleteOrphan();
HasMany(p => p.Time).Cascade.AllDeleteOrphan();
}
}
public class BillableTime
{
public virtual int Id { get; private set; }
public virtual Week Week { get; set; }
public virtual Person Person { get; set; }
public virtual Project Project { get; set; }
public virtual float[] DailyHours { get; set; }
public virtual Invoice Invoice { get; set; }
public virtual bool ReadOnly { get; set; }
}
public class BillableTimeMapping : ClassMap<BillableTime>
{
public BillableTimeMapping()
{
Id(x => x.Id);
References(x => x.Week);
References(x => x.Person);
References(x => x.Project);
References(x => x.Invoice);
Map(x => x.ReadOnly).Not.Nullable().Default("0");
Map(x => x.DailyHours).Length(28);
}
}
public class ExpenseReport
{
public virtual long Id { get; set; }
public virtual Person SubmittedBy { get; set; }
}
the following line should solve the issue, but i' dont know exactly why it is happening. if i have the spare time i will investigate.
HasMany(p => p.Time).Cascade.AllDeleteOrphan().KeyColumn("Person_Id");

add objects with child collections

I have two classes: a Site class and a Phase class. The Site class defines a collection of Phases. Each class corresponds to a database table. The database (SQL Server 2000) has a one to many reference between the two tables such that a given Site can be associated with many Phases but a given Phase can only be associated with a single Site.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public class Site
{
public virtual int Id {get; set;}
[Required]
[Editable(true)]
[StringLength(64)]
public virtual string Name { get; set; }
public virtual ICollection<Phase> Phases {get; set;}
}
public class Phase
{
public virtual int Id {get; set;}
[Required]
[Editable(true)]
[StringLength(64)]
public virtual string Name { get; set; }
[Editable(true)]
[StringLength(16)]
public virtual string Code { get; set; }
public virtual int SiteId {get; set;}
public virtual Site Site {get; set;}
}
I'm using FluentNHibernate to do my mapping. I want to map this in such away that I can create a new Site instance, assign a few Phase instances and make one call to get all instances into the database:
Site site = new Site() { Name = "SiteName" };
Phase phase = new Phase() { Name = "PhaseName", Code = "Code" };
Phase otherPhase = new Phase() { Name = "OtherPhaseName" };
site.Phases.Add(phase);
site.Phases.Add(otherPhase);
Session.SaveOrUpdate(site);
I have the following mappings in place, but they aren't doing the trick:
public class SiteMap : ClassMap<Site>
{
public SiteMap()
{
Id(p => p.Id).Column("ST_ID").GeneratedBy.Native();
Map(p => p.Name).Column("ST_Name");
HasMany<Phase>(x => x.Phases).KeyColumn("ST_ID").LazyLoad().Inverse().AsSet();
}
}
public class PhaseMap : ClassMap<Phase>
{
public PhaseMap()
{
Id(p => p.Id).Column("PH_ID").GeneratedBy.Native();
Map(p => p.Name).Column("PH_Name");
Map(p => p.Code).Column("PH_Code").Nullable();
Map(p => p.SiteId).Column("ST_ID");
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy).Not.Insert().Not.Update();
}
}
I'm new to NHibernate generally, so I recognize there may be other issues with the mappings shown here that I'm not aware of. Any assistance on how best to map these two classes would be appreciated. TIA.
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy).Not.Insert().Not.Update();
Should just be
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy);
Your Not Insert and Update specifications do not update this column when you're insert or update the table.
You also need to specify Cascade on your one to many end
HasMany<Phase>(x => x.Phases).KeyColumn("ST_ID").Cascade.AllDeleteOrphan().LazyLoad().Inverse().AsSet();

Fluent NHibernate with ManyToMany and Custom Link Table

I have the following schema, and when I delete one of the objects on one many side, it seems to be trying to delete the objects on the other many side. Am somewhat confused about the proper Cascade options to use, and I don't find Oren's brief description of them to be useful, so please don't quote those back.
public class Store {
public virtual IList<StoreProduct> StoreProducts { get; set; }
}
public class Product {
public virtual IList<StoreProduct> StoreProducts { get; set; }
}
public class StoreProduct {
public virtual Store Store { get; set; }
public virtual Product Product { get; set; }
public virtual Decimal Cost { get; set; } //this is why I have a custom linking class
}
In my mapping overrides, I have:
For Store:
mapping.HasMany(x => x.StoreProducts).Cascade.AllDeleteOrphan().Inverse;
For Product:
mapping.HasMany(x => x.StoreProducts).Cascade.AllDeleteOrphan().Inverse;
When I try to delete a Store that has associated StoreProducts, it seems that NHIbernate tries to delete not only the StoreProducts, but the Products.
Here are my conventions:
return c =>
{
c.Add<ForeignKeyConvention>();
c.Add<HasManyConvention>();
c.Add<HasManyToManyConvention>();
c.Add<ManyToManyTableNameConvention>();
c.Add<PrimaryKeyConvention>();
c.Add<ReferenceConvention>();
c.Add<EnumConvention>();
c.Add<TableNameConvention>();
c.Add<CascadeAll>();
c.Add(DefaultCascade.All());
};
HasManyConvention:
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.Column(instance.EntityType.Name + "Fk");
instance.Cascade.AllDeleteOrphan();
instance.Inverse();
}
What am I doing wrong?
Thanks!
p.s.: I don't want to overwhelm people w/code, but can post more if needed.
Thanks, CrazyDart - I think that is among the things I tried without success. What I ended up doing was adding a StoreProducts override that looks like this:
public class StoreProductOverride: IAutoMappingOverride<StoreProduct>
{
#region IAutoMappingOverride<StoreProduct> Members
public void Override(AutoMapping<IndicatorStrategy> mapping)
{
mapping.References(x => x.Store).ForeignKey("StoreFk").Cascade.SaveUpdate();
mapping.References(x => x.Producty).ForeignKey("ProductFk").Cascade.SaveUpdate();
}
#endregion
}
Seems to work, but QA hasn't tried to break it yet (-:
You need to turn off the cascading on StoreProduct is my guess. Its hard to test without setting it up. I see the cascade on Store and Product, but turn it off on StoreProduct.

Fluent NHibernate Adding and Updating problem : References

Im fairly n00bish when it comes to fluent nhibernate but i have an unexpected error in one of my repositories.
I have a datatype CostCode
public class CostCode
{
public virtual int Id { get; set; }
public virtual String CostCodeCode { get; set; }
public virtual Company Company { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual String CreatedBy { get; set; }
public virtual DateTime ModifiedDate { get; set; }
public virtual String ModifiedBy { get; set; }
}
and here is the mapping
public sealed class CostCodeMap : ClassMap<CostCode>
{
/**
* #breif Mapping Constructor
*/
public CostCodeMap()
{
Id(Reveal.Member<CostCode>("Id"));
Map(x => x.CostCodeCode).Not.Nullable();
References(x => x.Company, "CompanyId").Cascade.All();
Map(x => x.CreatedDate).Not.Nullable();
Map(x => x.CreatedBy).Not.Nullable();
Map(x => x.ModifiedDate).Not.Nullable();
Map(x => x.ModifiedBy).Not.Nullable();
}
}
When i try to update this, i get an error "identifier of an instance of Domain.DataTypes.Company was altered from 1 to 8"
Now i think its the way that i set up the mapping, and possibly how my repository is handling the updates/adds.
I have a drop down list that controls the id of the company, and when im adding/updating i set the property company to whatever is in the database for the id that it has been updated to.
var companyRepository= new CompanyRepository(_session);
temp.Company = companyRepository.GetCompanyById(temp.Company.Id);
_session.Update(c);
Can anyone give me a hint/solution to help me on my way? Looking through related problems here, the problem could be anything.
Ok, I will just throw this out... I bet what is happening is you are setting temp.Company.Id by changing the Id, then you use the repo to go fetch that company using the changed Id. NHibernate will track that you changed the Id on the other company however. Use a temp var to store that new company id, dont change the id of the other company.
I'm not 100% sure, but it really looks like maybe there is a bug here:
temp.Company = ...(temp.Company.Id);
I would figure you'd actually be pulling that from an incoming parameter.
Also, you can avoid a database hit here by using Session.Load():
temp.Company = _session.Load<Company>(passedInCompanyId);

How can I use Fluent NHibernate Automapping with multiple Lists of the same type in an Entity?

It appears that NHibernate cannot automap more than one IList of a given type in an entity.
Consider the following two entities (based on the Examples.FirstProject sample code that is included with the Fluent NHibernate source code).
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Store
{
public virtual int Id { get; private set; }
public virtual IList<Employee> Staff { get; set; }
public virtual IList<Employee> Managers { get; set; }
}
This seems to be a perfectly valid object model - each store has several staff employees and several manager employees.
But when I automap, the Staff and Managers lists are stored in the Employee table,all with the same foreign key.
Employee Table
Id FirstName LastName Store_id
3 Daisy Harrison 1
4 Jack Torrance 1
5 Sue Walkters 1
6 Tom Tommorow 1
7 Dick Diggler 1
The net result is that when the data is read back out of the database, both Staff and Managers lists are populated with every row in the table.
This looks like a bug in Automapping to me, but I'm fairly new to NHibernate in any form, and don't fully know it's limitations yet.
In any case, how can I make NHibernate treat the two lists as distinct?
If possible, I'd appreciate an Automapping code fragment that directly addresses the sample code I've provided (e.g. something like "put this exact override in the .Mappings section of your CreateSessionFactory").
This is because I'm only somewhat familiar with Automapping, and not at all familiar with the older ways of doing things, which means I can't "fill in the blanks" very well yet.
But if you only have time to point me in the right direction, that would be helpful too.
Here's my CreateSessionFactory code, to give some context:
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<Product>()
.Where(t => t.Namespace == "Examples.FirstProject.Entities")
.Conventions.Add( DefaultCascade.All() )
;
sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile)
.ShowSql()
)
.Mappings(m => m.AutoMappings.Add(autoPersistenceModel)
.ExportTo(autoMapExportDir)
)
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory()
;
}
catch (Exception e)
{
Console.WriteLine(e);
}
return sessionFactory;
}
Paul Batum answered my question here, and provided a standalone working example here (click the Download button after you navigate to the linked page).
The following code is copied from his answer. The key point is in the StoreMap class at the end of the listing, which sets up an override with a Where clause that uses the IsManager property in Employee.
Note that (at least with v. 1.0.0.594) there is one big gotcha with Automapping - the mapping class (e.g. StoreMap) cannot be in the same Namespace as the domain class (e.g. Store)!
Otherwise, NHibernate will throw "NHibernate.MappingException:
(XmlDocument)(2,4): XML validation error: ..." , with
absolutely no indication of what or where the real problem is.
This is probably a bug that may be fixed in later versions of Fluent NHibernate.
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual bool IsManager { get; set; }
}
public class Store
{
public virtual int Id { get; private set; }
public virtual IList<Employee> Staff { get; private set; }
public virtual IList<Employee> Managers { get; private set; }
public Store()
{
Staff = new List<Employee>();
Managers = new List<Employee>();
}
public void AddManager(Employee employee)
{
employee.IsManager = true;
this.Managers.Add(employee);
}
public void AddStaff(Employee employee)
{
this.Staff.Add(employee);
}
}
Here is the mapping override for store:
// Must be in different Namespace from class Store!!!
public class StoreMap : IAutoMappingOverride<Store>
{
public void Override(AutoMapping<Store> mapping)
{
mapping.HasMany(x => x.Managers)
.Cascade.All()
.Where("(IsManager = 1)");
mapping.HasMany(x => x.Staff)
.Cascade.All()
.Where("(IsManager = 0)");
}
}