Ayende on Fluent nHibernate - AutoMap

555 views
Skip to first unread message

Andrew Stewart

unread,
Aug 13, 2008, 6:14:21 AM8/13/08
to fluent-n...@googlegroups.com
Hello
 
I'm sure we've all seen this,
 
 
I think basically he wants automap built in, which we've already discussed.
 
So, I'd like to start a little discussion over how this would be implemented, I'm thinking something along the lines of:
 
var cfg = new Configuration()
               
.Configure()
               
.AutoMap(Assembly.LoadFrom("Name of assembly that contains your maps."));
 
or
 
var cfg = new Configuration()
               
.Configure()
               
.AutoMap("Namespace");
 
Ok, so thinking from here, I think the rest is quite straight forward. I'm thinking something along the lines of:
  • if the property has a setter then it's stored in nhibernate and is mapped.
  • If it contains a collection then it's mapped as many-to-one
  • If the class is inherits from a mapped type then it's a subclass
Now the sticking point, many-to-many joins? Any idea's?
 
Andy

James Gregory

unread,
Aug 13, 2008, 6:20:10 AM8/13/08
to fluent-n...@googlegroups.com
I dislike the use of extension methods on the NHibernate Configuration. We have a PersistenceModel which deals with all our setup and conventions, so I think the automap should be baked into that.

  • if the property has a setter then it's stored in nhibernate and is mapped.
  • If it contains a collection then it's mapped as many-to-one
  • If the class is inherits from a mapped type then it's a subclass
This all needs to be done through our convention API, and we can discuss what needs to go into the DefaultConvention.

I agree that we need to have a discussion about this. I'm in the process of evaluating what our convention support is like, and where we need to improve on it.

Andrew Stewart

unread,
Aug 13, 2008, 6:34:37 AM8/13/08
to fluent-n...@googlegroups.com
Hello
 
I dislike the use of extension methods on the NHibernate Configuration.
 
I actually disagree with this, as I think from a user coming to use the project then you want to make using the library as simple as possible. So links from the nhibernate configuration seems a natural place to put them. However your right in that the actual implementation should live in the PersistanceModel, as this is the correct SoC.
 
In my opinion I'd of thought the convention would be about naming conventions and defaults, rather than how you analyse the assmebly - suppose it depends on your definition though.
 
Anyway I'd just thought I'd start a discussion as to how we define these, as I'm quite keen to look into it.
 
Cheers
 
Andy
--
=================
I-nnovate Software - Bespoke Software Development, uk wirral.
http://www.i-nnovate.net

James Gregory

unread,
Aug 13, 2008, 6:42:49 AM8/13/08
to fluent-n...@googlegroups.com
In my opinion I'd of thought the convention would be about naming conventions and defaults, rather than how you analyse the assmebly - suppose it depends on your definition though.

Conventions currently are about naming and defaults, but they're also about types. So if we iterate through the types in an assembly, then run them through the conventions, we should be able to produce a mapping from them.


Anyway I'd just thought I'd start a discussion as to how we define these, as I'm quite keen to look into it.

I agree we need to discuss these ideas, but I'm not interested in knee-jerk reacting to this. We need to get our API implemented properly before we're even able to tackle this, there's no point implementing auto-mapping when we don't even fully support the normal mappings.

By all means though, discuss away. We need to decide how we're to approach this.

Andrew Stewart

unread,
Aug 13, 2008, 6:46:45 AM8/13/08
to fluent-n...@googlegroups.com
I agree we need to discuss these ideas, but I'm not interested in knee-jerk reacting to this. We need to get our API implemented properly before we're even able to tackle this, there's no point implementing auto-mapping when we don't even fully support the normal mappings.
 
Yeah I agree, it shouldn't be knee jerk but I do think we have enough implemented to do it. After all I don't think that automapping could do the advanced situation's and all the basic's are already implemented.
 
Your right though we need to look into getting the convention part polished all the same. As it would be nice to provide your own conventions which I don't think you can at the moment(haven't looked).
 
Andy

Andrew Stewart

unread,
Aug 13, 2008, 11:08:03 AM8/13/08
to fluent-n...@googlegroups.com
Ok
 
I've been playing round with this since I sent the first email, and 235 lines of code later(including tests). I've managed to write a layer that sit's over fluentnHibernate. That supports AutoMapping the following parts of your object:

new AutoMapIdentity(),
new AutoMapVersion(),
new AutoMapProperty(),
new AutoMapManyToOne(),
new AutoMapOneToMany(),

 
My next step is to get inheritance working, which seems straight forward enough, however I'm still struggling to think of a decent way to make many-to-many joins and one-to-one.
 
Anyone got any idea's, on naming conventions or anything similar.
 
Cheers
 
Andy

Jeremy D. Miller

unread,
Aug 13, 2008, 11:11:39 AM8/13/08
to fluent-n...@googlegroups.com
We've already got the baseline convention stuff for properties and attributes, so just let the automap reflect over the properties and add the mapping parts to the ClassMap.  The same convention code should take over from there.

It might be worthwhile to start extending the Conventions class independently of the automap. 

Another thing to think of is using the automapping on a class by class basis, but then letting the ClassMap override one or more properties.

I'd vote for avoiding many to many in the automapping.  I think that's where the automapping becomes more of a problem than a solution.
 
Jeremy D. Miller
The Shade Tree Developer
jeremy...@yahoo.com

Andrew Stewart

unread,
Aug 13, 2008, 2:35:05 PM8/13/08
to fluent-n...@googlegroups.com
Hi Jeremy
 
Thats pretty much how it does work. My automap just sits above the fluentnHibernate library and call's the exact same methods a user would, (which wasn't easy to figure out creating expressions at runtime). It just needs to hand off some responsibilities to the convention class to decide if the Id is the Indentifier or if the Version Property is the versioning attribute is etc. I can commit as a seperate project over fluentnhibernnate say fluentnhibernnate.AutoMapper or add it to the framework project if people would find it interesting.
 
The way I'm using at the moment is pretty much as you describe something close to
 
model
   .AutoMap<Address>()
   .AutoMap<Company>()
   .AutoMap<Contact>()
 
Then you can load in the rest of the maps the same way as you normally do.
 
Cheers
 
Andy

Kyle Baley

unread,
Aug 13, 2008, 3:21:57 PM8/13/08
to fluent-n...@googlegroups.com
Well, what're you waiting for? Check it in, man!

Andrew Stewart

unread,
Aug 13, 2008, 3:23:06 PM8/13/08
to fluent-n...@googlegroups.com
Doh, left it on the machine at work. Doh Doh Doh! It'll be in first thing in the morning, GMT that is.

James Gregory

unread,
Aug 13, 2008, 3:32:37 PM8/13/08
to fluent-n...@googlegroups.com
One thing I'm not sure about yet is the separation between regular mapping and auto-mapping. I've been wrestling with the idea of auto-mapping being the default, and explicit mapping be only for exceptional circumstances.

var model = new ExplicitMappedPersistenceModel();
model.AddMappingAssembly(asm);

That would interrogate assemblies for any types derived from ExplicitClassMap (currently ClassMap), and run as we've currently got it setup.

var model = new AutoMappedPersistenceModel();
model.AddMappingAssembly(asm, type => type.Namespace == "Product.Mapping");

However, the above example would auto-map all types found in the assembly (that match the expression), then also find any types of ExtendedClassMap and update any auto-maps with overrides from these types.

Thoughts?

Andrew: Nice work! By all means commit your stuff, I'm purely speculating here.

Andrew Stewart

unread,
Aug 14, 2008, 5:54:38 AM8/14/08
to fluent-n...@googlegroups.com
From what I've seen I can do do 80% of my classes with automap, and only had to override for Many-To-Many and inheritance although I think I can get inheritance working I just havent yet.
 
This is how it works at the moment
 
var model = new AutoPersistanceModel();
model.AutoMap<Address>()

   .AutoMap<Company>()
   .AutoMap<Contact>()
 
Now I quite like the look of what your suggesting, however when I asked a collegue he suggested he liked seeing exactly what was mapped and what wasn't(not sure I agree with him - but something to chew over)
 
var model = new AutoMappedPersistenceModel().
model.AddMappingAssembly(asm, type => type.Namespace == "Product.Mapping")
.Exclude<Address>();
 
Or as you say find all classMaps first and ignore those types, it doesnt do it at the mo but as I havent wrote those bits on mapping entire assemblies, yet.
 
Oh and I just checked in the AutoMapper
 
Cheers
 
Andy

Derick Bailey

unread,
Aug 14, 2008, 9:43:36 AM8/14/08
to Fluent NHibernate
@Andrew Stewart

I think you missed the files in the /Automap folder, on your
checkin... just updated, can't build now.

Andrew Stewart

unread,
Aug 14, 2008, 10:03:39 AM8/14/08
to fluent-n...@googlegroups.com
Sorry guys, was in meeting. Its in there now.
 
Andy

Jeremy Skinner

unread,
Aug 14, 2008, 11:11:25 AM8/14/08
to fluent-n...@googlegroups.com
FYI, there's a typo - it should be AutoPersistenceModel not AutoPersistanceModel

Jeremy

Andrew Stewart

unread,
Aug 14, 2008, 11:45:40 AM8/14/08
to fluent-n...@googlegroups.com
Lol, I always make that typo.
 
I'll get it corrected.

Andrew Stewart

unread,
Aug 14, 2008, 4:39:25 PM8/14/08
to fluent-n...@googlegroups.com
Just committed this experimental metthod:

var autoModel = new AutoPersistenceModel(Assembly.GetAssembly(typeof(AutoMapTests)));
autoModel.AddEntityAssembly(
Assembly.GetAssembly(typeof (AutoMapTests)),t => t.Namespace == "FluentNHibernate.AutoMap.Test");

Now that should add the ability to maps load all maps that are manually created, plus automap the other objects. Now here comes the problem I have at the moment, I have no easy way to tell what properties have been manually mapped, so that I can ignore them when automapping. Does anyone know of any easy way to find this out?

Cheers

Andy


Chad Myers

unread,
Aug 14, 2008, 6:24:00 PM8/14/08
to fluent-n...@googlegroups.com
Andrew,

Thanks for doing this. Cool stuff!

I was looking at the code, and I'm really not crazy about the "findMappings" business. that's really smelly. What was the intention here? I'm hoping we can find a better way of doing that. It feels like cheating to use string-based reflection in a project who's main intention is to solely use static reflection, haha!!!

When I think of Automapping, here's what I'm thinking:

var model = AutoPersistenceModel
.MapEntitiesFromAssemblyOf<AutoMapTests>()
.Where(t => t.Namespace == "FluentNHibernate.AutoMap.Test")
.ForTypesThatDeriveFrom<DomainEntity>(map =>
{
map.UseIdentityForKey(e => e.Id, "Id");
});


As far as dupe properties, I would expect that people would do AutoMap first, then provide explicit overrides. So the persistence model would have to be smart enough to know that a type was already mapped so when you map a property, it grabs the existing mapped property and merges the changes there.

Thoughts?

Chad

________________________________

From: fluent-n...@googlegroups.com on behalf of Andrew Stewart
Sent: Thu 8/14/2008 3:39 PM
To: fluent-n...@googlegroups.com
Subject: [fluent-nhib] Re: Ayende on Fluent nHibernate - AutoMap


winmail.dat

Gabriel Schenker

unread,
Aug 15, 2008, 12:55:24 AM8/15/08
to fluent-n...@googlegroups.com
this seems very reasonable to me. Automatically provide the defaults and then MERGE in the user specific mappings.

James Gregory

unread,
Aug 15, 2008, 3:31:17 AM8/15/08
to fluent-n...@googlegroups.com
That's how I saw it happening too.

Andrew Stewart

unread,
Aug 15, 2008, 4:05:42 AM8/15/08
to fluent-n...@googlegroups.com
Hi Chad
 
You'll be refereing to the below then:
 

var mapping = executeMethod(obj, this, "findMapping", null);

if (mapping != null)
{
  
executeMethod(obj, autoMap, "Map", new[] {mapping});
}
else
{
  
executeMethod(obj, autoMap, "Map", null);
}

Your right it does smell a tad, here's the problem, I need to execute a generic method either on the base class or on the automapper. However I don't know the type, as it's defined at runtime and this is the only way I could get it to work.

Now if anyone know's a way of calling these generic methods statically typed, I'll send them a big virtual hug as this has break me on refactoring written all over it.

Cheers

Andy

Andrew Stewart

unread,
Aug 15, 2008, 8:51:02 AM8/15/08
to fluent-n...@googlegroups.com
Hello
 
I've just checked this in and it seems to work,
 
var persistenceModel = new AutoPersistenceModel(Assembly.GetAssembly(typeof(PlotMap)));
persistenceModel.AddEntityAssembly(Assembly.GetAssembly(typeof (Address)), t => (t.Namespace == "Mdis.Entities" && ( t.BaseType == typeof(object))));
persistenceModel.Configure(cfg);
 
The first assembly is the assembly containing your maps, the second is the assembly containing your entities (I'm assuming you have two assemblies for SoC, but there's no reason why these can't be the same). Then I've created an AutoMap which inherits from ClassMap, this is so I don't dirty up the base class with my little requirements, essentially what properties have already been mapped(to prevent duplication).
 
Example

public class PlotMap : AutoMap<Plot>
{
  
public PlotMap()
  
{
     
HasManyToMany<Service>(p => p.Services);
  
}
}

What happens is, first it loads in your automap's then in maps in extra unmapped properties and entities(hope that makes sense). So you only need to specify exceptions to the rule everything else will be mapped straight out the box as if by magic. Now my next challenge will be making this a bit more fluent and specifying conventions across the board as of Chad's suggestion below:
 
 var model = AutoPersistenceModel
       .MapEntitiesFromAssemblyOf<AutoMapTests>()
       .Where(t => t.Namespace == "FluentNHibernate.AutoMap.Test")
       .ForTypesThatDeriveFrom<DomainEntity>(map =>
                                                 {
                                                     map.UseIdentityForKey(e => e.Id, "Id");
                                                 });
 
Plus if anyone can think of a nice way to replace the contents of AutoPersistanceModel.AddEntityAssembly, which strict typing let me know.
 
Hope you like it, so far.
 
Andy

Chad Myers

unread,
Aug 15, 2008, 8:02:46 PM8/15/08
to fluent-n...@googlegroups.com

I did a spike and got my head around it. Basically, you have a bunch of generic methods and then you’re treating them non-generically.

 

Will this be called directly by people, or is this mostly internal API stuff?

 

I was able to create an IAutoMap rather than have AutoMap<T>, IAutoPartMapper instead of the various part mappers (IAutoMapper was confusing because there’s an AutoMapper<T>, but it wasn’t an IAutoMapper, IAutoMapper is really something like IAutoPartMapper, etc).

 

Anyhow, if you use interfaces for these things and make them non-generic (i.e. don’t make it Foo<T>, just make methods like Foo(Type theType)).

 

Make the people-facing fluent interfaces generic <T>, but all the internal stuff non-generic otherwise your life will be very painful (as you’ve already found out).

 

I don’t want to commit my spike code because it’s not tested and it’s sloppy, but maybe we can pair on this sometime if you want, or if you want to research this yourself, etc.

 

-c

 

From: fluent-n...@googlegroups.com [mailto:fluent-n...@googlegroups.com] On Behalf Of Andrew Stewart
Sent: Friday, August 15, 2008 7:51 AM
To: fluent-n...@googlegroups.com
Subject: [fluent-nhib] Re: Ayende on Fluent nHibernate - AutoMap

 

Hello

Andrew Stewart

unread,
Aug 17, 2008, 10:28:06 AM8/17/08
to fluent-n...@googlegroups.com
Hi Chad
 
Will this be called directly by people, or is this mostly internal API stuff?
 
The crappy code is only internal and tests should now fail if it's broken, which is nice to know. I think I know what you mean and I'll try and tidy that code up shortly, thanks for the offer to pair over this it may come in useful, but i'd like to have a go at it myself for now. I'll give you a shout if I get stuck though, if it's ok with you.
 
Cheers
 
Andy


Paul Batum

unread,
Aug 17, 2008, 1:20:26 PM8/17/08
to fluent-n...@googlegroups.com
Hey Andy,

I decided to tackle your problem from a different angle to Chad. It sounds like Chad went to the root of the problem by making non generic methods. I came up with a less fragile way of invoking the generic methods using reflection. My solution replaces this:

// Merge Mappings together
var findAutoMapMethod = typeof(AutoMapper).GetMethod("MergeMap");
var genericfindAutoMapMethod = findAutoMapMethod.MakeGenericMethod(obj);
genericfindAutoMapMethod.Invoke(autoMap, new[] { mapping });

With this:

InvocationHelper.InvokeGenericMethodWithDynamicTypeArguments(
                autoMap, a => a.MergeMap<object>(null), new[] { mapping }, obj);

Here is the implementation of that method:

        public static object InvokeGenericMethodWithDynamicTypeArguments<T>(T target, Expression<Func<T, object>> expression, object[] methodArguments, params Type[] typeArguments)
        {
            var methodInfo = ReflectionHelper.GetMethod(expression);
            if (methodInfo.GetGenericArguments().Length != typeArguments.Length)
                throw new ArgumentException(
                    string.Format("The method '{0}' has {1} type argument(s) but {2} type argument(s) were passed. The amounts must be equal.",
                    methodInfo.Name,
                    methodInfo.GetGenericArguments().Length,
                    typeArguments.Length));

            return methodInfo
                .GetGenericMethodDefinition()
                .MakeGenericMethod(typeArguments)
                .Invoke(target, methodArguments);
        }
   
This is sturdier because a refactoring of the MergeMap method would flow to the lambda. Obviously it is still somewhat fragile because there is no guarantee that the right number of type arguments is passed, hence the runtime check.

The patch is attached to this email. I figure that you will probably want to go down the path that Chad followed, but perhaps this implementation will be useful trick when you don't have the luxury of modifying the API to be non-generic.

Paul Batum
reflection_fix.patch

Paul Batum

unread,
Aug 26, 2008, 4:04:21 PM8/26/08
to fluent-n...@googlegroups.com
Hey Andy,

I was wondering if you have decided where to go with this code? I thought my patch would be a good option if you don't want to engage in a larger refactoring.

Paul Batum

Andrew Stewart

unread,
Aug 27, 2008, 8:05:57 AM8/27/08
to fluent-n...@googlegroups.com
Hi Paul

I will be applying your patch as the first fix for the problem, however I've just not had any time yet. I may at somepoint look for a larger refactoring, but I think one way or another dropping down your method will be essential at somepoint in the process.
 
Cheers
 
Andy

Paul Batum

unread,
Aug 27, 2008, 4:51:01 PM8/27/08
to fluent-n...@googlegroups.com
Cool. I was a bit lazy and didn't write any tests for that code, I would like to go back and add a few once the patch is applied.

Andrew Stewart

unread,
Aug 27, 2008, 4:57:36 PM8/27/08
to fluent-n...@googlegroups.com
Hi Paul
 
There's a bunch of integration test's running over that series of code, which would test it from an automapping perspective. Maybe worth creating some a little more specific though.
 
Cheers
 
Andy

Paul Batum

unread,
Aug 27, 2008, 6:21:12 PM8/27/08
to fluent-n...@googlegroups.com
Yeah I wanted a few tests specifically for the method on InvocationHelper.

Andrew Stewart

unread,
Oct 6, 2008, 4:45:57 AM10/6/08
to fluent-n...@googlegroups.com
Hi Paul

First patch applied.

Cheers

Andy
Reply all
Reply to author
Forward
0 new messages