Try fast search NHibernate

Showing posts with label wasting time. Show all posts
Showing posts with label wasting time. Show all posts

13 May 2011

NHibernate: The bizarre Audit

This is another post tagged as “wasting time”; in this occasion thanks to Scott Findlater and Filip Kinský (@Buthrakaur).

The title is because there are people who think that having four properties to store a DateTime of an entity creation, the User who have created it, the DateTime of the last modification and the User who have modified it mean that his application has auditing.

If you are a NHibernate’s user you know that we are providing many events to catch and even override NHibernate behavior. As I said long time ago, just after implement all those events/listeners, "what you can do with writing or overriding NHibernate default events is limited only by your imagination" (sorry to be auto-referential).

In the NET you can find various examples about how implement a simple “audit” using NHibernate. Most of those implementations come from some very old JAVA examples based on IPreInsertEventListener and IPreUpdateEventListener. Are those examples completely wrong ? No, they aren’t wrong!! They are just some simple examples that may work or may not work in your case. Scott (Findlater) have pointed me to the fact that even in the “NHibernate 3.0 cookbook” there is an example using IPreInsertEventListener and IPreUpdateEventListener and that is true… as true is that there is another example without using NH’s events at all (page 244) and there is the full list of NH’s events (page 238).

IPreInsertEventListener and IPreUpdateEventListener

If you look inside NHibernate, you can see that NHibernate does not have a default implementation of those two events and this mean that, for NHibernate, those two events are there just for your usage (in NH nothing is less important than an interface without implementation). You can do various things with those events as just log changes (NHibernate 3.0 cookbook page 235) or vetoing the action (as we are doing in NHibernate.Validator). Can you change the internal state maintained by NHibernate ? yes because what you can do “is limited only by your imagination”; does it mean that NHibernate will follow your imagination even when you have no idea what mean its internal state and how work with it ? No, it just mean that your imagination about how work NHibernate may fail.

Your anxiety will not change the passing of events

What you should do if NHibernate does not follow your imagination ?

Well… the last thing to do is bore me with +20 public posts just to then discover that instead apply my solution you have interpreted it, and you have introduced a bug in your code, or write a WIKI (that is the abbreviation of “What I Know Is”) just to say that you don’t know why your issue was closed as “Duplicated” of an issue closed as “Not an issue” where the first user, after an explication with code included, has recognized that his interpretation had a bug. Instead this “little boy” behavior you can ask for help in the nhusers group then, perhaps, you can write a simple solution with some tests and where nobody can help you, you may send a mail to me asking, with a nice “please”, help for free… perhaps I’ll write a public blog post with your problem and the solution… perhaps.

The pour auditing listener

Some entities may implements this interface:

public interface ITrackModificationDate
{
    DateTime LastModified { get; set; }
}

Filip have published his domain and his test here, then he sent me a zip. I took his tests and I have added some more tests finding some bugs. The pour auditing listener passing all tests is this:

public class SetModificationTimeEventListener : IFlushEntityEventListener, ISaveOrUpdateEventListener, IMergeEventListener
{
    // WARNING : if you need to log dirty properties the work to do is another.

    public SetModificationTimeEventListener()
    {
        CurrentDateTimeProvider = () => DateTime.Now;
    }

    public Func<DateTime> CurrentDateTimeProvider { get; set; }

    public void OnFlushEntity(FlushEntityEvent @event)
    {
        var entity = @event.Entity;
        var entityEntry = @event.EntityEntry;

        if (entityEntry.Status == Status.Deleted)
        {
            return;
        }
        var trackable = entity as ITrackModificationDate;
        if (trackable == null)
        {
            return;
        }
        if (HasDirtyProperties(@event))
        {
            trackable.LastModified = CurrentDateTimeProvider();
        }
    }

    private bool HasDirtyProperties(FlushEntityEvent @event)
    {
        ISessionImplementor session = @event.Session;
        EntityEntry entry = @event.EntityEntry;
        var entity = @event.Entity;
        if(!entry.RequiresDirtyCheck(entity) || !entry.ExistsInDatabase || entry.LoadedState == null)
        {
            return false;
        }
        IEntityPersister persister = entry.Persister;

        object[] currentState = persister.GetPropertyValues(entity, session.EntityMode); ;
        object[] loadedState = entry.LoadedState;

        return persister.EntityMetamodel.Properties
            .Where((property, i) => !LazyPropertyInitializer.UnfetchedProperty.Equals(currentState[i]) && property.Type.IsDirty(loadedState[i], currentState[i], session))
            .Any();
    }

    public void OnSaveOrUpdate(SaveOrUpdateEvent @event)
    {
        ExplicitUpdateCall(@event.Entity as ITrackModificationDate);
    }

    public void OnMerge(MergeEvent @event)
    {
        ExplicitUpdateCall(@event.Entity as ITrackModificationDate);
    }

    public void OnMerge(MergeEvent @event, IDictionary copiedAlready)
    {
        ExplicitUpdateCall(@event.Entity as ITrackModificationDate);
    }

    private void ExplicitUpdateCall(ITrackModificationDate trackable)
    {
        if (trackable == null)
        {
            return;
        }
        trackable.LastModified = CurrentDateTimeProvider();
    }
}

This is not indented to be THE SOLUTION of every kind of similar situation. A similar situation may have a similar solution. This is the solution to pass some tests provided by Filip Kinský, that’s all.

The advanced Audit solution

(This section is dedicated to the master AJL to prevent his question)

If the above is a “pour Audit” which is the “rich Audit” ?

The rich audit using NHibernate was started by Simon Duduica, finished by Roger Kratz with some very little touch implemented by me.

The name of the most powerful audit system for NHibernate is: NHibernate.Envers

The code of NHibernate.Envers is in bitbucket.org : https://bitbucket.org/RogerKratz/nhibernate.envers

A presentation of its power is available in Spanish : http://www.altnethispano.org/wiki/van-2011-02-26-audit-parallel-model-con-nhibernate-3.ashx

What I should do if I want a “rich audit” without use NHibernate.Envers ?

You should study NHibernate.Envers and make it a little bit better, not worst, and then share your code.

Conclusion

1) Your anxiety will not change the passing of events.

2) when I say you that your issue is not a bug try to do everything possible to explain your case instead insist in your position.

3) If I send you some code, first use it as is, then run your tests, and only then try to make it “more elegant“ (red, green, think, refactor).

Post scriptum

I forgot to share the full configuration of NHibernate:

[TestFixtureSetUp]
public void BuildSessionFactory()
{
    listener =    new SetModificationTimeEventListener()
        {
            CurrentDateTimeProvider = () => defaultDate
        };

    var config = new Configuration();
    config.DataBaseIntegration(x =>
                               {
                                                             x.Dialect<MsSql2008Dialect>();
                                                             x.ConnectionStringName= "LocalForTests";
                                                             x.SchemaAction = SchemaAutoAction.Recreate;
                               });

    // Full mapping registration
    var mapper = new ConventionModelMapper();
    mapper.Class<Thing>(rcm => rcm.Bag(x => x.RelatedThings, map =>
                                                             {
                                                                                                                         map.Key(km=> km.OnDelete(OnDeleteAction.Cascade));
                                                                                                                         map.Cascade(Cascade.All.Include(Cascade.DeleteOrphans));
                                                                                                                         map.Inverse(true);
                                                             }));
    var mappings = mapper.CompileMappingFor(new[] { typeof(Thing), typeof(InheritedThing), typeof(RelatedThing) });
    config.AddDeserializedMapping(mappings,"HisDomain");

    // Events Liseners registration
    config.EventListeners.SaveEventListeners = new[] { listener }.Concat(config.EventListeners.SaveEventListeners).ToArray();
    config.EventListeners.SaveOrUpdateEventListeners = new[] { listener }.Concat(config.EventListeners.SaveOrUpdateEventListeners).ToArray();
    config.EventListeners.UpdateEventListeners = new[] { listener }.Concat(config.EventListeners.UpdateEventListeners).ToArray();
    config.EventListeners.MergeEventListeners = new[] { listener }.Concat(config.EventListeners.MergeEventListeners).ToArray();
    config.EventListeners.FlushEntityEventListeners = new[] { listener }.Concat(config.EventListeners.FlushEntityEventListeners).ToArray();

    sessionFactory = config.BuildSessionFactory();
}

that is NHibernate 3.2 sexy mapping Winking smile

18 April 2011

me on Fluent NHibernate

Well… from where I should start ?…?? Perhaps from “Mapping Source: How map a class without use XML” mmm… no, better no because it is too old and was classified as “too complicated”. Perhaps from “Map NHibernate using your API” mmm… no, better no because it is one year old and was classified as “too silly” as you can read in the comments of this post and in this other. What about this “NHibernate is notorious for having an obtuse and unfriendly API” … perhaps… perhaps… and what you think about this ?
frankly_jamesgregory
Would you help me to translate the above phrase ?

Or perhaps you want know something more ?
frankly_fm1
frankly_fm2
frankly_fm3
I can continue but frankly I have my ba…ehm.. sorry… I mean… I’m something tired of this situation.

Since the begin of FluentNHibernate the attitude was the same: say that NHibernate is a shit and you will be happy. Since the begin of FluentNHibernate we have asked a patch or something and the result was zero lines of code but each time I, or we (the team), do something, somebody come with the same aseptic critics without a single line of C# code. Which is the point ? What mean “Fluent NHibernate is smaller and more flexible than NHibernate” ? More than 600 *.cs files “only” to create a mapping is smaller than what ? an entirely ORM framework ? What is FNH without NHibernate ? bah?!??!?

I done ConfORM yes!! why ? I have asked to the team and the team said “if it will be not ready for 3.0 then please do it out from core” and in that time I was needing something to do a specific big work. Few days ago I have asked again and the team gave me its “OK!!”.

There are people arguing that ConfORM is the same of FluentNHibernate but with an ugly API. WOW!! THANKS!! you are saying that I done the same work, alone and in my free time within less than 3 months, with more flexibility and around 250 *.cs files instead more than 600. But are you really sure that I’m doing the same ? I think that ConfORM is more powerful than FNH but this is not the point. Each time I have published a post about ConfORM some FNH’s fanatics had the pleasure to ask me the same example but using FNH… ehi MAN!! do you haven’t something else to do ? FNH team ask me (not to the team but to me directly) that I have to implement a public API to let FNH avoid XMLs, FNH users ask to me to publish my examples about ConfORM but using FNH…when I implement another public API, to create mapping, again there is something else to do in another way…  are you crazy or what ?

The discussion about the API, of explicit mapping, used in NHibernate is again another point where people like to say something. That API was proposed to the team and the team have discussed it (and this is not the first time I’m saying the same as you can read here and here and here if you follow all links in those posts). We “imitate” the XML simplifying it where possible. If a user know XML then he can understand and follow the new API. All existing documentation is reusable. Which was the proposal of some FNH’s fanatic ? “you have to use the API of FNH instead that ugly API”… What ? Which will be the end of FluentNHibernate project if NH includes that API ?

All decisions was and are discussed by the team because NH not only has a big community but even is not “one man show”; NHibernate is a team!! We are following MVCs (Most Valuable Contributors) in our JIRA and around the NET and each year we invite some MVCs to become committers. Because I really care about NHibernate, so much that I have more than 1400 commits only in the core, I’m pushing people to implement more and more options to map it. I’m asking myself the reason because some FluentNHibernate fanatics are so blind to don’t see the point.

After these few words its time to show you the code. This time I’ll not use words as “Map NHibernate using your API” or “Map NHibernate using your DSL” (a vague reference to the recipient of the message) but… well… you will see.

Mapping class-by-class

The target is, using the high-level API of NHibernate 3.2, implement what is needed to use this other final API:
public class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        Id(x => x.Id);
        Map(x => x.FirstName);
        Map(x => x.LastName);
        References(x => x.Store);
    }
}

public class LocationMap : ComponentMap<Location>
{
    public LocationMap()
    {
        Map(x => x.Aisle);
        Map(x => x.Shelf);
    }
}

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.Price);
        HasManyToMany(x => x.StoresStockedIn)
            .Cascade.All()
            .Inverse()
            .Table("StoreProduct");

        Component(x => x.Location);
    }
}

public class StoreMap : ClassMap<Store>
{
    public StoreMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        HasManyToMany(x => x.Products)
            .Cascade.All()
            .Table("StoreProduct");
        HasMany(x => x.Staff)
            .Cascade.All()
            .Inverse();
    }
}
If you haven’t recognized it, the code is exactly the FluentNHibernate’s example available in FNH repository, the domain is obviously the same and the Program to run is pretty the same (I have to change the configuration to build the SessionFactory using the new implementations below).

All classes/interfaces needed to reproduce the Fluent-NHibernate’s API using the NHibernate 3.2 high-level API are:
public class ClassMap<T> : IMappingProvider where T : class
{
    private readonly ClassMapping<T> mapper = new ClassMapping<T>();

    public IdentityPart<T, TProperty> Id<TProperty>(Expression<Func<T, TProperty>> idProperty)
    {
        return new IdentityPart<T, TProperty>(mapper, idProperty);
    }

    public PropertyBuilder<T, TProperty> Map<TProperty>(Expression<Func<T, TProperty>> property)
    {
        return new PropertyBuilder<T, TProperty>(mapper, property);
    }

    public ManyToOneBuilder<T, TOther> References<TOther>(Expression<Func<T, TOther>> property) where TOther : class
    {
        return new ManyToOneBuilder<T, TOther>(mapper, property);
    }

    public IOneToManyPart HasMany<TChild>(Expression<Func<T, IEnumerable<TChild>>> property)
    {
        // TODO: In FNH you have to find the way to know the type of the collection (Bag,Set,List etc.);
        return new OneToManyPart<T, TChild, IBagPropertiesMapper<T, TChild>>(property, mapper.Bag);
    }

    public IManyToManyPart HasManyToMany<TChild>(Expression<Func<T, IEnumerable<TChild>>> property)
    {
        // TODO: In FNH you have to find the way to know the type of the collection (Bag,Set,List etc.);
        return new ManyToManyPart<T, TChild, IBagPropertiesMapper<T, TChild>>(property, mapper.Bag);
    }

    public void Component<TComponent>(Expression<Func<T, TComponent>> property) where TComponent: class
    {
        // TODO: In FNH you have to do the others stuff you need to return the "fluent" interface
        mapper.Component(property);
    }

    IConformistHoldersProvider IMappingProvider.GetNHibernateMapping()
    {
        return mapper;
    }
}

public class ComponentMap<T> : IMappingProvider where T : class
{
    private readonly ComponentMapping<T> mapper = new ComponentMapping<T>();

    public PropertyBuilder<T, TProperty> Map<TProperty>(Expression<Func<T, TProperty>> property)
    {
        return new PropertyBuilder<T, TProperty>(mapper, property);
    }

    IConformistHoldersProvider IMappingProvider.GetNHibernateMapping()
    {
        return mapper;
    }
}


public interface IMappingProvider
{
    IConformistHoldersProvider GetNHibernateMapping();
}

public class IdentityPart<T, TProperty> where T : class
{
    private readonly ClassMapping<T> mapper;
    private readonly Expression<Func<T, TProperty>> idProperty;

    public IdentityPart(ClassMapping<T> classMapping, Expression<Func<T, TProperty>> idProperty)
    {
        this.mapper = classMapping;
        this.idProperty = idProperty;
        mapper.Id(idProperty, x => { });
    }

    public IdentityPart<T, TProperty> Column(string columnName)
    {
        mapper.Id(idProperty, x => x.Column(columnName));
        return this;
    }
}

public class PropertyBuilder<T, TProperty> where T : class
{
    private readonly IPropertyContainerMapper<T> mapper;
    private readonly Expression<Func<T, TProperty>> property;

    public PropertyBuilder(IPropertyContainerMapper<T> classMapping, Expression<Func<T, TProperty>> property)
    {
        this.mapper = classMapping;
        this.property = property;
        mapper.Property(property, x => { });
    }

    public PropertyBuilder<T, TProperty> Column(string columnName)
    {
        mapper.Property(property, x => x.Column(columnName));
        return this;
    }
}

public class ManyToOneBuilder<T, TOther>
    where T : class
    where TOther : class
{
    private readonly ClassMapping<T> mapper;
    private readonly Expression<Func<T, TOther>> property;

    public ManyToOneBuilder(ClassMapping<T> classMapping, Expression<Func<T, TOther>> property)
    {
        this.mapper = classMapping;
        this.property = property;
        mapper.ManyToOne(property, x => { });
    }

    public ManyToOneBuilder<T, TOther> Column(string columnName)
    {
        mapper.ManyToOne(property, x => x.Column(columnName));
        return this;
    }
}

public interface ICollectionAttributesApplier<T, TChild> where T : class
{
    void ApplyAttributes(Action<ICollectionPropertiesMapper<T, TChild>> apply);
}

public interface IOneToManyPart
{
    ICollectionCascadeExpression<IOneToManyPart> Cascade { get; }
    IOneToManyPart Inverse();
}

public class OneToManyPart<T, TChild, TCollectionCustomizer> : ICollectionAttributesApplier<T, TChild>, IOneToManyPart
    where T : class
    where TCollectionCustomizer : ICollectionPropertiesMapper<T, TChild>
{
    private readonly Expression<Func<T, IEnumerable<TChild>>> property;
    private readonly Action<Expression<Func<T, IEnumerable<TChild>>>, Action<TCollectionCustomizer>, Action<ICollectionElementRelation<TChild>>> customizer;

    public OneToManyPart(Expression<Func<T, IEnumerable<TChild>>> property,
                         Action<Expression<Func<T, IEnumerable<TChild>>>, Action<TCollectionCustomizer>, Action<ICollectionElementRelation<TChild>>> customizer)
    {
        this.property = property;
        this.customizer = customizer;
        ApplyRelation(x => x.OneToMany());
    }

    public ICollectionCascadeExpression<IOneToManyPart> Cascade
    {
        get { return new CollectionCascadeExpression<IOneToManyPart, T, TChild, OneToManyPart<T, TChild, TCollectionCustomizer>>(this, this); }
    }

    public IOneToManyPart Inverse()
    {
        ApplyAttributes(x => x.Inverse(true));
        return this;
    }

    public void ApplyAttributes(Action<ICollectionPropertiesMapper<T, TChild>> apply)
    {
        customizer(property, att => apply(att), rel => { });
    }

    private void ApplyRelation(Action<ICollectionElementRelation<TChild>> apply)
    {
        customizer(property, x => { }, apply);
    }
}

public interface IManyToManyPart
{
    ICollectionCascadeExpression<IManyToManyPart> Cascade { get; }
    IManyToManyPart Table(string tableName);
    IManyToManyPart Inverse();
}

public class ManyToManyPart<T, TChild, TCollectionCustomizer> : ICollectionAttributesApplier<T, TChild>, IManyToManyPart
    where T : class
    where TCollectionCustomizer : ICollectionPropertiesMapper<T, TChild>
{
    private readonly Expression<Func<T, IEnumerable<TChild>>> property;
    private readonly Action<Expression<Func<T, IEnumerable<TChild>>>, Action<TCollectionCustomizer>, Action<ICollectionElementRelation<TChild>>> customizer;

    public ManyToManyPart(Expression<Func<T, IEnumerable<TChild>>> property,
                          Action<Expression<Func<T, IEnumerable<TChild>>>, Action<TCollectionCustomizer>, Action<ICollectionElementRelation<TChild>>> customizer)
    {
        this.property = property;
        this.customizer = customizer;
        ApplyRelation(x => x.OneToMany());
    }

    public ICollectionCascadeExpression<IManyToManyPart> Cascade
    {
        get { return new CollectionCascadeExpression<IManyToManyPart, T, TChild, ManyToManyPart<T, TChild, TCollectionCustomizer>>(this, this); }
    }

    public IManyToManyPart Inverse()
    {
        ApplyAttributes(x => x.Inverse(true));
        return this;
    }

    public IManyToManyPart Table(string tableName)
    {
        ApplyAttributes(x => x.Table(tableName));
        return this;
    }

    public void ApplyAttributes(Action<ICollectionPropertiesMapper<T, TChild>> apply)
    {
        customizer(property, att => apply(att), rel => { });
    }

    private void ApplyRelation(Action<ICollectionElementRelation<TChild>> apply)
    {
        customizer(property, x => { }, apply);
    }
}

public interface ICollectionCascadeExpression<TParent>
{
    TParent All();
    TParent DeleteOrphan();
    TParent AllDeleteOrphan();
}

public class CollectionCascadeExpression<TParent, TContainer, TChild, TApplier> : ICollectionCascadeExpression<TParent>
    where TParent : class
    where TContainer : class
    where TApplier : ICollectionAttributesApplier<TContainer, TChild>
{
    private readonly TParent parent;
    private readonly TApplier applier;

    public CollectionCascadeExpression(TParent parent, TApplier applier)
    {
        this.parent = parent;
        this.applier = applier;
    }

    public TParent All()
    {
        applier.ApplyAttributes(x => x.Cascade(Cascade.All));
        return parent;
    }

    public TParent DeleteOrphan()
    {
        applier.ApplyAttributes(x => x.Cascade(Cascade.DeleteOrphans));
        return parent;
    }

    public TParent AllDeleteOrphan()
    {
        applier.ApplyAttributes(x => x.Cascade(Cascade.All.Include(Cascade.DeleteOrphans)));
        return parent;
    }
}

The code used for the method to build the SessionFactory is:
private static ISessionFactory CreateSessionFactory()
{
    var configure = new Configuration();
    configure.DataBaseIntegration(x =>
    {
        x.Dialect<MsSql2008Dialect>();
        x.ConnectionString = (new SqlConnectionStringBuilder
        {
            DataSource = @"localhost\SQLEXPRESS",
            InitialCatalog = "reFNH",
            IntegratedSecurity = true
        }).ToString();
        x.SchemaAction = SchemaAutoAction.Recreate;
    });
    return configure.AddMappingsFromAssemblyOf<Program>().BuildSessionFactory();
}

The extension method to add all mappings using the new implementations is:
public static Configuration AddMappingsFromAssemblyOf<T>(this Configuration conf)
{
    var mapper = new ModelMapper();
    mapper.BeforeMapClass += (mi, t, cam) => cam.Id(x => x.Generator(Generators.Native));

    foreach (Type type in typeof(T).Assembly.GetExportedTypes().Where(x => typeof(IMappingProvider).IsAssignableFrom(x) && !x.IsGenericTypeDefinition && !x.IsInterface))
    {
        IMappingProvider mappingInstance;
        try
        {
            mappingInstance = (IMappingProvider)Activator.CreateInstance(type);
        }
        catch (Exception e)
        {
            throw new MappingException("Unable to instantiate mapping class (see InnerException): " + type, e);
        }
        mapper.AddMapping(mappingInstance.GetNHibernateMapping());
    }

    conf.AddDeserializedMapping(mapper.CompileMappingForAllExplicitAddedEntities(), "Domain");
    return conf;
}

Conclusion

Now not only you have the API you was asking for 3 years (implemented as we need) but you have even the starting point to use it in Fluent-NHibernate and put the turbo to your framework. If you want/need you can use a more low-level API; the name space where look is NHibernate.Mapping.ByCode.Impl .If you need something else, please let me know.

P.S. About “”auto”” mapping I’ll be back soon.

03 March 2010

Opinions about ConfORM

I have received a curious opinion about ConfORM through Twitter. For some reason the twitt was probably deleted but I have the image:

JeremyMillerTwitt

Nice to meet you Jeremy!!

Some fact

This is part of my first post about ConfORM:

The API proposed in the previous post is pretty good but the underlining implementation is too much strongly typed to be reused in a non-strongly-typed task.

I was thinking in begin a new proof-of-concept but two weeks ago somebody have asked me to write ~400 mappings of a domain created from various XSD… perhaps to join business requirement and Open-Source pleasure is not so hard.

The motivation around is pretty clear and I hope that your mind is enough open to understand “the underlining implementation is too much strongly typed to be reused in a non-strongly-typed task” and which is the non-strongly-typed task.

In the NHibernate’s development list you can read this mail:

Hi all.
I'm going to begin the implementation of "Loquacious mapping" in the trunk.
- API proposed in my blog (generics and strongly typed)
- *Mapper implementation as studied in ConfORM (namespaces ConfOrm.NH
and ConfOrm.Mappers ), neither generic nor strongly typed.

Thoughts ?

Again in the NHibernate’s development list you can read this mail:

> What benefit is this if FluentNH already does something similar?

Before somebody else continue with the same question I would like to have an
answer to these questions:
Which is the benefit/differences between:
Win7, MacOsX, Umbutu... ?
NUnit, MbUnit, xUnit ?
Spring.Net, Castle.Windsor, LinFu, AutoFact, Unity, StructureMap, Funq ?
MsSQL, Oracle, MySql, PostGres, FireBird, SqLite.... ?
Log4Net, NLog, EL-Logging ?
LinFu-AOP, Castle-AOP, Spring-AOP, PostSharp, Cecil ?
J#, C#, VB.NET, Delphi.NET, IronRuby, IronPython ?

When somebody will explain me, in detail, why we should have so many options
to do the same I will explain why I want start a different project.

Conclusion

Part of ConfORM is a study of some classes you will see in NHibernate3.0 and are oriented to those people want write something to map NHibernate (including Fluent-NHibernate).

We (NHibernate team) heard the phrase “Instead of making the necessary structural improvements” and the answer is the same Ayende gave more than one year ago: “a patch is welcome”. We was waiting a patch or a proposal and we are still waiting.

I never said why use or not use Fluent-NHibernate because for me is an option to map a domain for NHibernate, as others options. To have many options to achieve the same goal is the heart, or may be the consequence, of the evolution.

Take it easy and be happy!!