FluentNhibernate and References - fluent-nhibernate

I was trying to change a convention so that my IDs follow this simple rule: ProductCode, CustomerCode, OrderCode etc etc.
I've found a simple way to do that adding a convention:
public class PrimaryKeyNameConvention : IIdConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name + "Code");
}
}
Now I've got what I wanted but it seems that FluentNhibernate refuses to apply the same rule with column referencing my primary keys.
EX: my table Customer will have a PK called CustomerCode but my table Order will have a reference column called Customer_Id.
I've tried different ways to rename the column Customer_Id in CustomerCode (table Order) but it seems that nothing works properly.
The only solution which seems to work is adding a convention like this:
public class ReferenceConvention : IReferenceConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance)
{
instance.Column(instance.Property.PropertyType.Name + "Code");
}
}
but now FluentNhibernate creates two columns which reference my primary key: CostumerCode and Customer_Id.
I can't figure out what I am doing wrong.
Any help would be apreciated.
Regards,
Alberto

Take a look at the ForeignKeyConvention base-class.
The ForeignKeyConvention is an amalgamation of several other conventions to provide an easy way to specify the naming scheme for all foreign-keys in your domain. This is particularly useful because not all the foreign-keys are accessible in the same way, depending on where they are; this convention negates need to know about the underlying structure.

As James suggested I've now applied these two conventions:
public class PrimaryKeyNameConvention : IIdConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name + "Code");
}
}
public class CustomForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if (property == null)
return (type.Name + "Code"); // many-to-many, one-to-many, join
return (property.Name + "Code"); // many-to-one
}
}
and everything works fine.

Related

Fluent NHibernate mapping on multiple non-PK fields

I have a situation similar to that described in Fluent NHibernate Mapping not on PK Field
However, the relationship between my tables is described by multiple non-primary key columns.
Imagine Chris Meek's situation but where a Person has a JobType and a Code that, together, should (sorry, it's a legacy database) uniquely describe a Person
Person
------
Id PK
JobType
Code
Name
Order
-----
Id PK
Person_JobType
Person_Code
OrderDetails
Serhat Özgel's answer describes using PropertyRef, but I can't find a way to do that individually for multiple columns. I've tried similar to
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
HasMany(p => p.Order)
.KeyColumns.Add("Person_JobType")
.PropertyRef("JobType")
.KeyColumns.Add("Person_Code")
.PropertyRef("Code")
}
}
But this obviously doesn't work, since KeyColumns.Add() returns another OneToManyPart so PropertyRef() isn't being run against the individual column being added. The second PropertyRef() simply overwrites the first one, and I get the following error:
NHibernate.MappingException : collection foreign key mapping
has wrong number of columns: MyApp.Person.Order type: Int32
I've looked at the various overloads of KeyColumns.Add(),
public TParent Add(string name)
public TParent Add(params string[] names)
public TParent Add(string columnName, Action<ColumnPart> customColumnMapping)
public TParent Add(ColumnMapping column)
Specifically the last two, but couldn't find any way to set PropertyRef individually level for each column :(
Is there a way to do that? Am I going about this the wrong way entirely?
using hbm.xml and FluentNHibernate it is possible with a trick
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(_ => JobTypeAndCode)
.Columns.Add("Person_JobType", "Person_Code")
.ReadOnly()
.LazyLoad() // optional: prevent loading the Columns twice
.Access.None();
HasMany(p => p.Orders)
.KeyColumns.Add("Person_JobType", "Person_Code")
.PropertyRef("JobTypeAndCode")
}
private object JobTypeAndCode { get; set; } // FakeProperty
}
Note: i never got this to work using NHibernate MappingByCode

How can I stop Fluent NHibernate automapping from creating foreign keys across the database?

I am using latest version of Fluent NHibernate automapping. Is there any convention or property I can set to stop creating the foreign key constraints across all the tables? I have nearly 200 classes, So I cannot go to each individual class and property name and set
ForeignKeyConstraintNames("none", "none")
How can we add ForeignKeyConstraintNames("none", "none") in Automapping? I don't want to hardcode the table name or column name. I would like to have the AutoMapping create all the mappings without foreign keys. Basicall don't create any foreign keys across the database. How can we do this?
There is similar POST HERE but the answer was not clear to me.
a simple convention
public class NoForeignKeys : IReferenceConvention, IHasManyConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.ForeignKey("none");
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.ForeignKey("none");
}
}
// use it
AutoMap.AssemblyOf().Conventions
.FromAssembly() or .Add(typeof(NoForeignKeys))

Can auto mappings conventions work with mapping overrides?

I have a convention for my ids, which automatically maps properties with a name of Id as the identifier. As requirements are being fleshed out I need to tweak a domain model so naturally I went online and found that I need to create a class that inherits from IAutoMappingOverride<T>.
My convention:
public class PrimaryKeyConvention : IIdConvention, IIdConventionAcceptance
{
public void Apply(IIdentityInstance instance)
{
instance.Column("Id");
instance.GeneratedBy.SeqHiLo(instance.Name, "10");
}
public void Accept(IAcceptanceCriteria<IIdentityInspector> criteria)
{
criteria.Expect(x => x.Generator, Is.Not.Set);
}
}
My override:
public class LocateMappingOverride : IAutoMappingOverride<Locate>
{
public void Override(AutoMapping<Locate> mapping)
{
mapping.Map(x => x.SendTo).Not.Nullable();
}
}
The convention does work as expected if I remove my override.
The exception I get is The entity 'LocateMappingOverride' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)..
Is it possible to use conventions in conjunction with mapping overrides?
The answer is - yes, automapping can work with overrides.
Look what the error said. The problem is not with Locate entity, but with LocateMappingOverride entity, and that class should not be treated as entity, of course. You must have IAutomappingConfiguration configured so that FluentNHibernate's rule what to treat as entity includes LocateMappingOverride, too. And it does not have an Id mapped, indeed.
You should either:
change your IAutomappingConfiguration so that classes that implements IAutoMappingOverride<> are excluded
move the override outside the scope that is searched for entities
or introduce a common marker interface that all entities need to implement, i.e. IEntity and change IAutomappingConfiguration rules respectively.

How do you automap List<float> or float[] with Fluent NHibernate?

Having successfully gotten a sample program working, I'm now starting
to do Real Work with Fluent NHibernate - trying to use Automapping on my project's class
heirarchy.
It's a scientific instrumentation application, and the classes I'm
mapping have several properties that are arrays of floats e.g.
private float[] _rawY;
public virtual float[] RawY
{
get
{
return _rawY;
}
set
{
_rawY = value;
}
}
These arrays can contain a maximum of 500 values.
I didn't expect Automapping to work on arrays, but tried it anyway,
with some success at first. Each array was auto mapped to a BLOB
(using SQLite), which seemed like a viable solution.
The first problem came when I tried to call SaveOrUpdate on the
objects containing the arrays - I got "No persister for float[]"
exceptions.
So my next thought was to convert all my arrays into ILists e.g.
public virtual IList<float> RawY { get; set; }
But now I get:
NHibernate.MappingException: Association references unmapped class: System.Single
Since Automapping can deal with lists of complex objects, it never
occured to me it would not be able to map lists of basic types. But
after doing some Googling for a solution, this seems to be the case.
Some people seem to have solved the problem, but the sample code I
saw requires more knowledge of NHibernate than I have right now - I
didn't understand it.
Questions:
1. How can I make this work with Automapping?
2. Also, is it better to use arrays or lists for this application?
I can modify my app to use either if necessary (though I prefer
lists).
Edit:
I've studied the code in Mapping Collection of Strings, and I see there is test code in the source that sets up an IList of strings, e.g.
public virtual IList<string> ListOfSimpleChildren { get; set; }
[Test]
public void CanSetAsElement()
{
new MappingTester<OneToManyTarget>()
.ForMapping(m => m.HasMany(x => x.ListOfSimpleChildren).Element("columnName"))
.Element("class/bag/element").Exists();
}
so this must be possible using pure Automapping, but I've had zero luck getting anything to work, probably because I don't have the requisite knowlege of manually mapping with NHibernate.
Starting to think I'm going to have to hack this (by encoding the array of floats as a single string, or creating a class that contains a single float which I then aggregate into my lists), unless someone can tell me how to do it properly.
End Edit
Here's my CreateSessionFactory method, if that helps formulate a
reply...
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<DlsAppOverlordExportRunData>()
.Where(t => t.Namespace == "DlsAppAutomapped")
.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)
{
Debug.WriteLine(e);
}
return sessionFactory;
}
I would probably do a one to many relationship and make the list another table...
But maybe you need to rethink your object, is there also a RawX that you could compose into a RawPoint? This would make a table with 3 columns (ParentID, X, Y).
The discontinuity comes from wanting to map a List to a value that in an RDBMS won't go in a column very neatly. A table is really the method that they use to store Lists of data.
This is the whole point of using an ORM like NHibernate. When doing all the querying and SQL composition by hand in your application, adding a table had a high cost in maintenance and implementation. With NHibernate the cost is nearly 0, so take advantage of the strengths of the RDBMS and let NHibernate abstract the ugliness away.
I see your problem with mapping the array, try it with an override mapping first and see if it will work, then you could maybe create a convention override if you want the automap to work.
.Override<MyType>(map =>
{
map.HasMany(x => x.RawY).AsList();
})
Not sure if that will work, I need to get an nHibernate testing setup configured for this stuff.
Since I posted my question, the Fluent NHibernate team have fixed this problem.
You can now automap ILists of C# value types (strings, ints, floats, etc).
Just make sure you have a recent version of FNH.
Edit
I recently upgraded from FNH 1.0 to FNH 1.3.
This version will also automap arrays - float[], int[], etc.
Seems to map them as BLOBs. I assume this will be more efficient than ILists, but have not done any profiling to confirm.
I eventually got an override to work - see the end of the code listing. The key points are:
a new mapping class called DlsAppOverlordExportRunDataMap
the addition of a UseOverridesFromAssemblyOf clause in
CreateSessionFactory
Also, it turns out that (at least with v. 1.0.0.594) there is a very big gotcha with Automapping - the mapping class (e.g. DlsAppOverlordExportRunDataMap) cannot be in the same Namespace as the domain class (e.g. DlsAppOverlordExportRunData)!
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, and may be fixed in later versions of Fluent NHibernate.
namespace DlsAppAutomapped
{
public class DlsAppOverlordExportRunData
{
public virtual int Id { get; set; }
// Note: List<float> needs overrides in order to be mapped by NHibernate.
// See class DlsAppOverlordExportRunDataMap.
public virtual IList<float> RawY { get; set; }
}
}
namespace FrontEnd
{
// NEW - SET UP THE OVERRIDES
// Must be in different namespace from DlsAppOverlordExportRunData!!!
public class DlsAppOverlordExportRunDataMap : IAutoMappingOverride<DlsAppOverlordExportRunData>
{
public void Override(AutoMapping<DlsAppOverlordExportRunData> mapping)
{
// Creates table called "RawY", with primary key
// "DlsAppOverlordExportRunData_Id", and numeric column "Value"
mapping.HasMany(x => x.RawY)
.Element("Value");
}
}
}
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<DlsAppOverlordExportRunData>()
.Where(t => t.Namespace == "DlsAppAutomapped")
// NEW - USE THE OVERRIDES
.UseOverridesFromAssemblyOf<DlsAppOverlordExportRunData>()
.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)
{
Debug.WriteLine(e);
}
return sessionFactory;
}
Didn't get any answers here or on the Fluent NHibernate mailing list that actually worked, so here's what I did.
It smells like a horrible hack, but it works. (Whether it will scale up to large data sets remains to be seen).
First, I wrapped a float property (called Value) in a class:
// Hack - need to embed simple types in a class before NHibernate
// will map them
public class MappableFloat
{
public virtual int Id { get; private set; }
public virtual float Value { get; set; }
}
I then declare the properties in other classes that need to be Lists of floats e.g.
public virtual IList<MappableFloat> RawYMappable { get; set; }
NHibernate creates a single database table, with multiple foreign keys, e.g.
create table "MappableFloat" (
Id integer,
Value NUMERIC,
DlsAppOverlordExportRunData_Id INTEGER,
DlsAppOverlordExportData_Id INTEGER,
primary key (Id)
)

NHibernate - Do I have to have a class to interface with a table?

I have a class called Entry. This class as a collection of strings called TopicsOfInterest. In my database, TopicsOfInterest is represented by a separate table since it is there is a one-to-many relationship between entries and their topics of interest. I'd like to use nhibernate to populate this collection, but since the table stores very little (only an entry id and a string), I was hoping I could somehow bypass the creation of a class to represent it and all that goes with (mappings, configuration, etc..)
Is this possible, and if so, how? I'm using Fluent Nhibernate, so something specific to that would be even more helpful.
public class Entry
{
private readonly IList<string> topicsOfInterest;
public Entry()
{
topicsOfInterest = new List<string>();
}
public virtual int Id { get; set; }
public virtual IEnumerable<string> TopicsOfInterest
{
get { return topicsOfInterest; }
}
}
public class EntryMapping : ClassMap<Entry>
{
public EntryMapping()
{
Id(entry => entry.Id);
HasMany(entry => entry.TopicsOfInterest)
.Table("TableName")
.AsList()
.Element("ColumnName")
.Cascade.All()
.Access.CamelCaseField();
}
}
I had a similar requirement to map a collection of floats.
I'm using Automapping to generate my entire relational model - you imply that you already have some tables, so this may not apply, unless you choose to switch to an Automapping approach.
Turns out that NHibernate will NOT Automap collections of basic types - you need an override.
See my answer to my own question How do you automap List or float[] with Fluent NHibernate?.
I've provided a lot of sample code - you should be able to substitute "string" for "float", and get it working. Note the gotchas in the explanatory text.