property filter with fluent nHibernate automapping - nhibernate

i'm trying to create a filter, using fluent nH (1.2) automapping with nH 2.1.2.
I've followed the example here, but I keep getting the exception:
filter-def for filter named 'DateFilter' was never used to filter classes nor collections..
the filter class:
public class DateFilter : FilterDefinition
{
public DateFilter()
{
WithName(Consts.FilterConsts.DATE_FILTER)
.AddParameter("date", NHibernate.NHibernateUtil.DateTime)
.WithCondition("DATEPART(dayofyear,EntityTime) = DATEPART(dayofyear, :date)")
;
}
}
and in the mapping override:
mapping.HasMany(x => x.Stuff)
.LazyLoad()
.ReadOnly()
.ApplyFilter<DateFilter>();
here's my configuration code.
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.DefaultSchema("dbo") //set default schema to enable full-qualified queries
.AdoNetBatchSize(batchSize > 0 ? batchSize : 1)
.UseReflectionOptimizer()
.ConnectionString(c => c.FromConnectionStringWithKey(connectionStringKey))
.Cache(c => c.UseQueryCache()
.ProviderClass(
isWeb ? typeof(NHibernate.Caches.SysCache2.SysCacheProvider).AssemblyQualifiedName //in web environment- use sysCache2
: typeof(NHibernate.Cache.HashtableCacheProvider).AssemblyQualifiedName //in dev environmet- use stupid cache
))
)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Domain.Entity>(cfg) //automapping the domain entities
.IncludeBase<Domain.SomethingBase>() //ensure that although SomethingBase is a base class, map it as well. this enables us to store all Something sub-classes in the same table
.IncludeBase<Domain.OrOtherBase>() //create a table for the abstract 'OrOtherBase' class
.UseOverridesFromAssemblyOf<MappingOverrides.MappingOverride>()
.Conventions.Add(DefaultCascade.All()) //make sure that all saves are cascaded (i.e when we save a zone, its queues are saved as well)
.Conventions.AddFromAssemblyOf<IdGenerationWithHiLoConvention>()
))
.Mappings(m => m.FluentMappings.Add(typeof(DateFilter)));
if I move the line before the automapping part, I get the exception:
NHibernate.MappingException: filter-def for filter named 'DateFilter' was not found.
can anybody tell me what I'm doing wrong?

OK, so I figured this out. When you add the mappings separately like that, they end up in different mappings and it will either complain that you never use the filter or complain that it can't find the filter, because it's not looking both places. The solution is to add it directly to the automap, so in your case like:
//other stuff up here
.Mappings(m => m.AutoMappings.Add(() => {
var a = AutoMap.AssemblyOf<Domain.Entity>(cfg)
.IncludeBase<Domain.SomethingBase>() //and also cascades and conventions and stuff
a.Add(typeof(DateFilter));
return a;
}));
Kinda gross because .Add() isn't fluent, but it does work.

Related

Fluent Nhibernate - ClassMaps in multiple, separate assemblies

Consider the following Fluent configuration;
FluentConfiguration config = Fluently.Configure();
config.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager.ConnectionStrings[dbKey].ConnectionString));
// MemberMap is in the same assembly as this class, contains
// maps for member and role entities as part of a default
// membership service provider
MappingAssemblies.Add(typeof(MemberMap).Assembly);
foreach (Assembly mappingAssembly in MappingAssemblies)
{
// For each assembly that has been added to MappingAssemblies
// we add to the current mapping configuration. This allows
// me to drop this helper into any solution and it provide
// standardized membership abilities AS WELL AS manage
// the solutions unique ClassMaps
config.Mappings(m =>
m.FluentMappings.AddFromAssembly(mappingAssembly)
);
}
if (exportSchema)
{
config.ExposeConfiguration(cfg =>
{
new SchemaExport(cfg)
.Create(true, true);
}
);
}
_sessionFactory = config.BuildSessionFactory();
This logic is held within a static class I call from within my Global.asax on application startup. The startup configuration will look something similar to;
Database.MappingAssemblies.Add(typeof(PageContentMap).Assembly);
// This is the method detailed above
Database.FluentConfigureSessionFactory("MySolutionsDb", true);
So the idea is that I have packaged my Member and Role entity objects into the same assembly as the Database helper object so that any solution I care to create can instantly gain my standardized membership abilities as well as being able to simply create its own solution specific ClassMaps and add them to the configuration object.
The issue is, the familiar call to;
config.Mappings(m =>
m.FluentMappings.AddFromAssembly(mappingAssembly)
);
only seems to be able to deal with a single assembly. Doesn't matter what is added to the list, only the last assembly added will be mapped. As an alternative to the above, I have tried holding a reference to MappingConfiguration (which is what 'm' stands for in the config.Mappings(m => ) ) but this did not work either. It is obvious that such a call to m.FluentMappings.AddFromAssembly or indeed any of the FluentMappings.Add methods will overwrite what is previously there but surely there is a way of getting this done ? It doesn't seem like that much of a 'weird' requirement.
Old question, but I managed to solve it after looking at this, so I'll try to answer it. This is how I did (the .ForEach() is an extension from NHibernate.Linq):
config.Mappings(m => MappingAssemblies.ForEach(a => m.FluentMappings.AddFromAssembly(a)))
I had to do it for auto-mapped stuff as well, and there the syntax is a bit different. I have an interface that marks all classes I want to automap:
config.Mappings(m =>
m.AutoMappings.Add(AutoMap.Assemblies(MappingAssemblies.ToArray())
.Where(x => x.GetInterfaces().Contains(typeof(IAutoMappedEntity)))))
Also, I don't have a "MappingAssemblies" that I set manually, I took the lazy approach of just including all my assemblies, so my config looks like this (using SQLite, this is from a test project):
var MappingAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.FullName.StartsWith("MyCompanyName."));
Configuration configuration;
var sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory())
// This adds fluent mappings
.Mappings(m => MappingAssemblies.ForEach(a => m.FluentMappings.AddFromAssembly(a)))
// This adds automapped classes using specific configuration
.Mappings(m => m.AutoMappings.Add(AutoMap.Assemblies(new MyAutomapConfiguration(), MappingAssemblies)))
// This adds automapped classes that just use my magic interface
.Mappings(m => m.AutoMappings.Add(AutoMap.Assemblies(MappingAssemblies.ToArray()).Where(x => x.GetInterfaces().Contains(typeof(IAutoMappedEntity)))))
.ExposeConfiguration(cfg => configuration = cfg)
.BuildSessionFactory();

NHibernate Search Index poco object throws TransientObjectException

When calling Index method on FullTextSession with plain poco object throws the error below, works fine with proxied object.
Stacktrace:
[TransientObjectException: the instance was not associated with this session]
NHibernate.Impl.SessionImpl.GetIdentifier(Object obj) +500
I'm trying to squeeze the performance out of the nhibernate select method I've got the following code:
public virtual IList<T> LoadSearch()
{
return Adapater.Session.QueryOver<T>()
.SelectList(e =>
{
e.Select(x => x.Id);
e.Select(x => x.Title);
e.Select(x => x.Description);
return e;
}).List<object[]>()
.Select(props => new T
{
Id = (Guid)props[0],
Title = (string)props[1],
Description = (string)props[2]
}).ToList();
}
Is there way to return a proxied result? or some how adapt the list to a proxied list?
I think you can only index objects that are associated with a session, i.e. proxied entities.
The plain POCOs you are returning didn't come from NH - so aren't associated with a NH session.
You could try using ISession.Lock(instance, NHibernate.LockMode.None); on each entity to associate it with the session, but I really don't know if that'd work.

How to configure multiple mappings using FluentHibernate?

First time rocking it with NHibernate/Fluent so apologies in advance if this is a naive question. I have a set of Models I want to map. When I create my session factory I'm trying to do all mappings at once. I am not using auto-mapping (though I may if what I am trying to do ends up being more painful than it ought to be). The problem I am running into is that it seems only the top map is taking. Given the code snippet below and running a unit test that attempts to save 'bar', it fails and checking the logs I see NHibernate is trying to save a bar entity to the foo table. While I suspect it's my mappings it could be something else that I am simply overlooking.
Code that creates the session factory (note I've also tried separate calls into .Mappings):
Fluently.Configure().Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c
.Server(#"localhost\SQLEXPRESS")
.Database("foo")
.Username("foo")
.Password("foo")))
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers
.Table.Is(x => "foos"));
m.FluentMappings.AddFromAssemblyOf<BarMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers
.Table.Is(x => "bars"));
})
.BuildSessionFactory();
Unit test snippet:
using (var session = Data.SessionHelper.SessionFactory.OpenSession()) {
var bar = new Bar();
session.Save(bar);
Assert.NotNull(bar.Id);
}
You're doing it wrong. :)
Firstly, m.FluentMappings.AddFromAssemblyOf<FooMap>() and m.FluentMappings.AddFromAssemblyOf<BarMap>() are doing exactly the same thing (if FooMap and BarMap are in the same assembly). Each one just tells Fluent NHibernate to scan the assembly that contains the generic type; so if both types are in the same assembly, it'll scan it twice.
Secondly, the Conventions call is not scoped to the specific assembly you call it after, it's for the whole set of mappings. So what you're doing is supplying two conventions to set the table name to an explicit value, and the second one is the last one to be applied. What you want to do is use the x parameter (which is the entity type) and create your table name from that.
What you need is something like this:
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(Table.Is(x => x.Name + "s"));
})
Obviously my implementation is naive, and depending on what your table naming convention is you might want to use a pluraliser (I believe Castle has one, but it shouldn't be hard to find one with google).
You can read up more about conventions on the Fluent NHibernate wiki.
With classmap you specify the table name in the mapping. If not specified, it will be the same as the entity class name.
class FooMap : ClassMap<Foo>
{
public FooMap()
{
Table("foos");
}
}
Conventions apply to all mappings. As you added 2 table name conventions, only 1 will take effect.
Are your FooMap and BarMap in the same assembly? You only need to add each assembly once.
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<FooMap>())
Just want to share this will automatically add plural name either s, es, ies based on the end alphabet. Not so sure about grammar and some exception but this seems ok for me. Any exception use Table("foos") like #Lachlan Roche said. E.g. Customer class will have Customers table and Category class will have Categories table.
Modified #James Gregory answer:
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<FooMap>()
.Conventions.Add(Table.Is(x => GetPluralName(x.Name));
})
public static string GetPluralName(string oldName)
{
// This is the very simple. Just ignore exception like days, boys, photos and other specific nouns.
if (oldName.EndsWith("y"))
return oldName.Remove(oldName.Length - 1) + "ies";
else if (oldName.EndsWith("s") || oldName.EndsWith("e") || oldName.EndsWith("h") || oldName.EndsWith("z") || oldName.EndsWith("o")) // Sibilant consonant or "o"
return oldName + "es";
return oldName + "s";
}

FluentNHibernate Automapping not generating mappings

I'm attempting to use Fluent NHibernate auto mappings for the first time. It appears that the code I'm using isnt generating any mappings. It has been pretty much copy-pasted from the Auto mapping wiki page.
var mappings = AutoMap
.AssemblyOf<MvcBugs.Model.Project>();
mappings.WriteMappingsTo("c:\\temp\\mappings");
var sessionFactory = Fluently.Configure()
.Mappings(m => m.AutoMappings.Add(mappings))
.Database(SQLiteConfiguration.Standard.InMemory())
.ExposeConfiguration(c => { new SchemaExport(c)
.SetOutputFile("c:\\temp\\schema.sql")
.Create(false, true); })
.BuildSessionFactory();
(also would someone create an auto-mapping tag, I'm too new or something)
this appears to be a bug. Removing the line:
mappings.WriteMappingsTo("c:\\temp\\mappings");
Makes the mappings get set up correctly.

Custom naming conventions in Fluent NHibernate AutoMapping

according to this Post it is possible to change the naming convention from "[TableName]_id" to "[TableName]ID". However when I saw the code, I wasn't able to do it with my rather new (about 6 weeks old) version of Fluent NHibernate.
Original code:
var cfg = new Configuration().Configure();
var persistenceModel = new PersistenceModel();
persistenceModel.addMappingsFromAssembly(Assembly.Load("Examinetics.NHibernate.Data"));
persistenceModel.Conventions.GetForeignKeyNameOfParent = type => type.Name + "ID";
persistenceModel.Configure(cfg);
factory = cfg.BuildSessionFactory();
I came up with this:
PersistenceModel model = new PersistenceModel();
model.AddMappingsFromAssembly(assemblyType.Assembly);
model.ConventionFinder.Add(
ConventionBuilder.Reference.Always(part
=> part.ColumnName(part.EntityType.Name + part.Property.Name)));
configuration.ExposeConfiguration(model.Configure);
return configuration.BuildConfiguration();
but this gives me the original Table, because EntityType is not the referenced Entity and I see no property to get the "parent" entity.
How can I do this?
See The answer about removing underscores in Ids using Fluent NHibernate Automapping conventions here.
A portion of the text, the tables and classes are in the link.
Solution:
Add a convention, it can be system wide, or more restricted.
ForeignKey.EndsWith("Id")
Code example:
var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
.Database(/* database config */)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Product>(cfg)
.Conventions.Setup(c =>
{
c.Add(ForeignKey.EndsWith("Id"));
}
)
.BuildSessionFactory();
Now it will automap the ShelfId column to the Shelf property in Product.