views:

631

answers:

4

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; 
    }
+1  A: 

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.

joshperry
@Josh - Thanks for the quick response. I kinda see where you're going, but not quite enough to actually start experimenting. Could you edit your answser with a small code fragment, to elaborate on "I would probably do a one to many relationship..."? Also, note that I'm doing pure automapping - I don't have any explicit entity mapping classes (but I suppose I could add them if that's the only way to solve the problem - if only I knew how ;-).
Tom Bushell
Added an override sample that may work for a property of IList<float>, give it a try.
joshperry
@Josh - I tried to do overrides on all the IList<float> propertiesin my object model, but am still getting "...unmapped class: System.Single" exceptions.
Tom Bushell
A: 

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)
)
Tom Bushell
+1  A: 

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; 
    }
Tom Bushell
+1  A: 

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.

Tom Bushell