Ignore column using mapping by code in HNibernate - nhibernate

I'm using mapping by code in NHibernate.
I got a class with several properties. One of them is not related to any columns in DB but still has getter and setter.
I use ConventionModelMapper not ModelMapper. The first one assumes that all properties are mapped.
How i can tell to NHibernate to ignore it?

I find it easier to just create an attribute, attach that attribute to the property, and check for it in the mapper.IsPersistentProperty method. Something like this:
class IngnoreAttribute : Attribute
{
}
class Foo
{
[Ignore]
public virtual string Bar { get; set; }
}
mapper.IsPersistentProperty((mi, declared) => mi.GetCustomAttribute<IgnoreAttribute>() == null);
This way, I don't have to keep a list of properties to be ignored at the mapping codes.

Why not map the properties you want and leave the ones not needed to be mapped
check this
You can manage the persistence of ConventionModelMapper as following:
mapper.BeforeMapProperty += (mi, propertyPath, map) =>
{
// Your code here using mi, propertyPath, and map to decide if you want to skip the property .. can check for property name and entity name if you want to ignore it
};
A better answer would be:
mapper.IsPersistentProperty((mi, declared) =>
{
if (mi.DeclaringType == typeof (YourType) && mi.Name == "PropertyNameToIgnore")
return false;
return true;
});

If you do not mention the property that should be ignored in your NHibernate mapping, NHibernate will ignore it.

Related

Querying with linq a collection mapped as a map (IDictionary)

Using NHibernate, I have a collection of entities mapped as a dictionary.
By example, class A has a collection of B named Children, mapped as a IDictionary<int, B>. B has a property Name.
Querying class A base on some condition on B children unrelated to their dictionary indexation is quite straightforward with HQL:
from A where A.Children.Name = 'aName'
Runs flawlessly.
But for achieving the same with LINQ, this is quite less straightforward:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Fails with message could not resolve property: Values of: B
So yes, we can trick it through
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
That does works and yields expected results.
But this looks to me a bit ugly, I would rather not have to do that 'invalid' cast (at least 'invalid' outside of linq2NH context).
Is there any better way for querying a children collection mapped as a IDictionary with Linq and NHibernate?
As an exercise, I have decided to extend linq-to-nhibernate for supporting Values. This give a solution to the question.
There is many ways for extending linq2NH, see this list. Here, I need to add a new 'generator', as in my answer to another question.
First you need a bunch of using:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
Then, implement HQL translation for Values.
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
Extend the default linq2NH registry with your generator:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
I had quite a hard time figuring out how to register a generic class property generator. There is built-in support for many cases including generic dictionaries methods through derived class of GenericDictionaryRuntimeMethodHqlGeneratorBase, but apparently no support for generic dictionaries properties. So I have ended up 'hard coding' it in the TryGetGenerator method for properties.
Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
Now this does work:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Disclaimer: done only as an exercise, I have not even committed that in my actual code. I am currently no more using any map in my mappings. I have added some just for testing, then I have undone all.

Property which should be a readonly db-field (Fluent NHibernate Automapping)

Im using the Fluent NHibernate together with the automapping functionality. Now im lookin' for something like a configuration, setting, custom attribute - whatever - to declare an entity property as "ReadOnlyFromDb"
In the MsSql database im using a computed column in one of my tables where a value is calculated depending on some other values of the specific data row. Now I have declared this column in the entity class as
public virtual int STATUS { get; private set; }
On getting the specific data of the table everything works fine. The property STATUS is filled correct with the specific value in the datatable.
The problem occures when i try to SaveUpdate() the specific object.
I always get the exception
A computed column cannot be the target of an INSERT or UPDATE statement
Which is correct for my understanding - and its how it was supposed to be ;)!
Basically im looking for a configuration, setting, custom attribute - whatever - to say
Hey Fluent NHibernate - get the specific property propertyName but do not insert / update the property propertyName
Is there something like that? Or does a workaround exists for this case?
I've searched the fluent nhibernate wiki but have not found a smilliar case.
I hope that someone has already faced and solved this problem!
Here is the code snippet how I create the session (maybe it helps):
public static ISessionFactory GetNHibernateSession()
{
if (nhibernateSession != null)
return nhibernateSession;
if (ConfigurationManager.AppSettings[msSqlConnectionString] == null || ConfigurationManager.AppSettings[msSqlConnectionString] == String.Empty)
throw new NullReferenceException(String.Format("AppSetting '{0}' must not be null!", msSqlConnectionString));
//nhibernateSession = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConfigurationManager.AppSettings[msSqlConnectionString]))
// .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<T_ABWEICHUNGEN>().IgnoreBase<BaseClass>)).BuildSessionFactory();
nhibernateSession = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConfigurationManager.AppSettings[msSqlConnectionString]))
.Mappings(m => m.AutoMappings.Add(AutoMap.Assembly(System.Reflection.Assembly.GetExecutingAssembly())
.Override<T_ABWEICHUNGEN>(map =>map.Map(d => d.A08_STATUS_FILE).Length(2147483647))
.IgnoreBase(typeof(BaseClass))
.IgnoreBase(typeof(IDColumn))
.IgnoreBase(typeof(MsSqlQuery))
.IgnoreBase(typeof(MsSqlParamCollection))
.IgnoreBase(typeof(AbweichungStatus))
)).BuildSessionFactory();
return nhibernateSession;
}
}
thanks so far for the response - helped me so far.
But - is there a way to get this solved more 'dynamicly'?
For example:
I want to declare a custom attribute called [ReadOnlyDbField] now declare all properties of the entity with this cusomt attribute to say: Just read this value and do not update / insert it.
Basically i want to say in the configuration
Map all properties with the custom attribute [ReadOnlyDbField] to Not.Insert().Not.Update()
Is there a way to implement this?
.Override<Entity>(map => map.Map(d => d.STATUS).Not.Insert().Not.Update())
update: account for edited question
public class ReadonlyDbFielConvention : AttributePropertyConvention<ReadOnlyDbField>
{
protected override void Apply(ReadOnlyDbField attribute, IPropertyInstance instance)
{
instance.Not.Insert();
instance.Not.Update();
}
}

How to exclude a single class from being mapped when using an IAutomappingConfiguration

I'm using AutoMappings with Fluent NHibernate and would like to exclude a single class from being mapped. Because I'm using an IAutomappingConfiguration, I apparently cannot use a "Where" clause. For example, the following can not be used in this situation:
.Where(t => t != typeof(ClassToExclude))
So is there a way to accomplish the exclusion when using an IAutomappingConfiguration? (As a last resort, I could move the class to a different assembly, but I'm looking for an answer that doesn't require this.)
class MyAutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type != typeof(ClassToExclude);
}
}
and use
AutoMap.AssemblyOf<SomeEntity>(new MyAutomappingConfiguration())

NHibernate ExcludeProperty: Why is there no IncludeProperty

The Example class is great if you are specifying which properties you want to exclude from the example. But what if you want to specify which properties to include?
Take this example: looking for people in the database that have the same name.
A Person object has many properties. So to use the NHibernate.Criterion.Example object I would have to specify every field to exclude - which could be many.
Why is there no IncludeProperty method?
I have a Person object and I want to see if it is a duplicte based on pre-set business rules (FirstName, LastName, DateOfBirth). These rules could be changed to include a postcode or something else - and I'd like to make that configurable.
Is there an easy way around this?
I have a solution to the IncludeProperty issue:
private Type persitentType = typeof(T);
public IList<T> GetByExample(T exampleInstance, params string[] propertiesToInclude)
{
// get the properties that will be excluded
List<string> propertiesToExclude =
persitentType.GetProperties().Where(p => propertiesToInclude.Contains(p.Name) == false).Select(p => p.Name).ToList();
// create the criteria based on the example and excluding the given properties
ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
Example example = Example.Create(exampleInstance);
foreach (string propertyToExclude in propertiesToExclude)
{
example.ExcludeProperty(propertyToExclude);
}
criteria.Add(example);
// return the result
return criteria.List<T>();
}
Add this method to your repository class. It uses reflection to determine what properties the specified object has, and then finds the properties to exclude based on those that have been specified as includes.

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)
)