(Fluent) NHibernate - Inhertiance on object level but not on table level - nhibernate

I have the following idea:
Business object implemented as interface or abstract class with certain properties as read only to all layers except the DAL layer. I also want my business objects in another assembly than the DAL (for testing purposes), so marking the properties is not an option for me.
Examples could be one to one relationships or other properties.
I have almost solved the issue by doing the following
abstract class User
{
public virtual long UserId {get; protected set;}
public virtual string Password {get; protected set;}
...
}
In the DAL:
public class DbUser : User
{
internal virtual void SetPassword(string password) {...}
}
I then map this using fluent as
ClassMap<User> {...}
SubclassMap<DbUser> {...}
The problem I get is that fluent tries to create a table named DbUser.
If I skip the SubclassMap and creates a DbUser object and tries to save it I get an "No persister for this object" error.
Is it possible to solve?

You could probably override what is done with Fluent
public class DbUser: IAutoMappingOverride<DbUser>
{
public void Override(AutoMapping<DbUser> mapping)
{
//tell it to do nothing now, probably tell it not to map to table,
// not 100% on how you'd do this here.
}
}
Or you could have an attribute
public class DoNotAutoPersistAttribute : Attribute
{
}
And in AutoPersistenceModelGenerator read for attribute in Where clause to exclude it.
Check would be something like
private static bool CheckPeristance(Type t) {
var attributes = t.GetCustomAttributes(typeof (DoNotAutoPersistAttribute), true);
Check.Ensure(attributes.Length<=1, "The number of DoNotAutoPersistAttribute can only be less than or equal to 1");
if (attributes.Length == 0)
return false;
var persist = attributes[0] as DoNotAutoPersistAttribute;
return persist == null;
}
Then it kind of depends how you're adding entities but you're probably adding via assembly so this might do it for you:
mappings.AddEntityAssembly(typeof(User).Assembly).Where(GetAutoMappingFilter);
....
...
private static bool GetAutoMappingFilter(Type t)
{
return t.GetInterfaces().Any(x => CheckPeristance(x)); //you'd probably have a few filters here
}

Related

Fluent NHiberate Automapping - inheritance per table not writing discriminator column

Using
NHibernate : 3.3.2
Fluent NHibernate: 1.3.0
.NET 4.0
Hi all, I'm trying to put together a (very) simple reference project for Fluent NHibernate using automapping, in particular setting up table-per-hierarchy inheritance. I've tried copying the config from an existing (working) project and I've run through the example on the Fluent Wiki page on AutoMapping and inheritance and both give me the same result; the base class that I've set up with table-per-hirearchy gets treated like a regular class.
The domain model looks like so:
namespace Tests
{
public abstract class Animal
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int Legs { get; set; }
}
public class Cat : Animal {}
public class Budgie : Animal {}
}
Like I said, simple, just to illustrate inheritance. I'm aware that 'legs' should probably be overridden on the base classes :)
The AutoMapping configuration looks like so:
public class AutoMappingConfig : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
var include = type.IsSubclassOf(typeof(Animal));
Debug.WriteLineIf(include, string.Format("Included {0} in NHibernate mapping.", type));
return include;
}
public override bool IsDiscriminated(Type type)
{
var result = type.In(
(typeof(Cat)),
(typeof(Budgie))
);
return result;
}
}
And finally, the configuration/session creation looks like so:
public static ISession NewSession()
{
var cfg = new AutoMappingConfig();
var map = AutoMap.AssemblyOf<Animal>(cfg)
.IgnoreBase<Animal>();
var dbConfiguration = MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["testdb"].ConnectionString);
return Fluently.Configure()
.Mappings(m =>m.AutoMappings.Add(map))
.Database(dbConfiguration)
.BuildSessionFactory()
.OpenSession();
}
Putting that together, I try to create a couple of new records:
[Test]
public void CreateData()
{
var tiddles = new Cat {Name = "Tiddles", Legs = 4};
var kylie = new Budgie {Name = "Kylie", Legs = 2};
using (var transaction = _session.BeginTransaction())
{
_session.Save(tiddles); // exception!
_session.Save(kylie);
transaction.Commit();
}
}
}
The error is:
NHibernate.Exceptions.GenericADOException : could not insert:
[Tests.Cat][SQL: INSERT INTO [Cat] (Name, Legs) VALUES (?, ?); select
SCOPE_IDENTITY()] ----> System.Data.SqlClient.SqlException : Invalid
object name 'Cat'.
A few things to note:
The '?' are being filled out if I check with SQL Profiler.
When I put a breakpoint in the IsDiscriminated(Type type) method I can see that
its being called with the two expected types (Cat & Budgie) and is returning true each time. However, the SQL is writing to the wrong table, and its NOT writing a discriminator column. i.e. even though its been told that these classes are discriminated, they're not being treated as such.
In the table, the Id column is an auto-increment identity column.
I've tried adding other properties to the two sub classes in case they needed something other than just the base properties to trigger the correct behavior (no difference).
Any help would be greatly appreciated. I'm now convinced its something obvious, but no one here knows much about NHibernate (LightSpeed is another matter) so I've no idea what.
Ok, so the final working code looks like this:
public class AutoMappingConfig : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
var include = type.IsSubclassOf(typeof(Animal)) || type == typeof (Animal);
Debug.WriteLineIf(include, string.Format("Included {0} in NHibernate mapping.", type));
return include;
}
public override bool IsDiscriminated(Type type)
{
return typeof(Animal).IsAssignableFrom(type);
}
}
public static ISession NewSession()
{
var cfg = new AutoMappingConfig();
var map = AutoMap.AssemblyOf<Animal>(cfg)
.IncludeBase<Animal>();
var dbConfiguration = MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["testdb"].ConnectionString);
return Fluently.Configure()
.Mappings(m =>m.AutoMappings.Add(map))
.Database(dbConfiguration)
.BuildSessionFactory()
.OpenSession();
}
And all is well with the world :)
(i.e. there were three errors)
The instructions here are a bit confusing, as it first talks about using .IgnoreBase<> so NHibernate wont treat the base as entity in its own right, then later mentions using .Includebase<> when using abstract layer supertypes. I'd tried both, but without Firo's answer got no luck.
you forgot animal
public override bool IsDiscriminated(Type type)
{
return typeof(Animal).IsAssigneableFrom(type);
}

Fluent NHibernate ShouldMap does not detect my custom attribute

I have been spending a couple of days now to get to know the Fluent NHibernate automapping working model. It is quite nice, but I keep detecting new details missing from my schemas. Now I want to add extra properties to my classes, but not have them mapped to the database. A typical case is when I need extra properties with internal logic.
So I read the examples and scanned StackOverflow and found out that this was not another convention to be added, but rather a matter of inheriting the DefaultAutomappingConfiguration and override the ShouldMap method.
Fine, no problem, one minute later I had something like this:
public class CustomAutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Member member)
{
var explicitSkip = member.PropertyType.GetCustomAttributes(typeof(SkipMap), false).Length > 0;
if ((member.IsProperty && !member.CanWrite) || explicitSkip)
{
return false;
}
return base.ShouldMap(member);
}
}
/// <summary>
/// Don't map this property to database.
/// </summary>
public class SkipMap : Attribute
{
}
public class DemoClass
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual MyBitwiseEnum Status { get; set; }
public virtual bool IsValid
{
get
{
return (int)Status > 3;
}
}
[SkipMap]
public virtual bool IsBad
{
get
{
return MyBitwiseEnum.HasFlag(MyBitwiseEnum.Bad);
}
set
{
MyEnum = value ? MyBitwiseEnum | MyBitwiseEnum.Bad : MyBitwiseEnum ^ MyBitwiseEnum.Bad;
}
}
}
I know that my demo class is kind of stupid, but it will illustrate my point.
The idea is that I want to manually decide what properties to map to database.
The readonly property works fine because the ShouldMap method will look for property.CanWrite. But the custom attribute that definitely is set will not be detected. Why is that!?
In the convention methods I have used the same approach frequently and there it works fine. Why is the property not able to detect defined attributes here, when it obviously can in the convention setting. Is there a workaround?
have you added your new automapconvention to Automap?
AutoMap.AssemblyOf<>(new CustomAutomappingConfiguration())
Update: you are getting the skip attribute from Boolean class instead of the property
member.PropertyType.GetCustomAttributes(typeof(SkipMap), false)
should be
member.MemberInfo.GetCustomAttributes(typeof(SkipMap), false)
Just to be sure the custom attribute is applicable to properties, try adding [AttributeUsage(AttributeTargets.Property)] to your SkipMap class.
Another possibility is an attribute name clash with another attribute that applies to different targets. Try renaming the class to something like MyVerySpecialSkipMap and retest to verify you don't have an attribute clash. At the very least, write some simple reflection code to test for the SkipMap attribute outside the context of your application to ensure it can be found.

Fluent nhibernate automapping collection

I am trying to map my collections with FNHib automapping. The problems that I want to solve are:
1) I want all my collections in the project to be mapped via private field. How can I say that globally?
2) Is there any way to automap bidirectional relationship without explicitly overriding each of my entities.
class OrganizationEntity example:
private ISet<> _collectionWarehouse;
public virtual IEnumerable<WarehouseEntity> CollectionWarehouse
{
get{return _collectionWarehouse; }
set{_collectionWarehouse = new HashedSet<WarehouseEntity>((ICollection<WarehouseEntity>)value)}
}
Class WarehouseEntity example:
public virtual OrganizationEntity Organization{get;set;}
You can map your collections to a private field 'globally' with the following convention:
// assumes camel case underscore field (i.e., _mySet)
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance) {
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
Whenever you want to set a 'global' automap preference in FNH, think conventions. The you use the IAutoOverride on a given class map if you need to.
As far has the set (a HashSet is usually what I really want also) part, the last time I had to do some mapping, I did need to do an override, like:
public class ActivityBaseMap : IAutoMappingOverride<ActivityBase>
{
public void Override(AutoMapping<ActivityBase> m)
{
...
m.HasMany(x => x.Allocations).AsSet().Inverse();
}
}
I do agree that should translate into a convention though, and maybe you can do that these days. Please post if you figure it out.
HTH,
Berryl
CODE TO USE A HASHSET as an ICollection =================
public virtual ICollection<WarehouseEntity> Wharehouses
{
get { return _warehouses ?? (_warehouses = new HashSet<WarehouseEntity>()); }
set { _warehouses = value; }
}
private ICollection<WarehouseEntity> _warehouses;

How to map an interface in nhibernate?

I'm using two class NiceCustomer & RoughCustomer which implment the interface ICustomer.
The ICustomer has four properties. They are:
Property Id() As Integer
Property Name() As String
Property IsNiceCustomer() As Boolean
ReadOnly Property AddressFullText() As String
I don't know how to map the interface ICustomer, to the database.
I get an error like this in the inner exception.
An association refers to an unmapped class: ICustomer
I'm using Fluent and NHibernate.
You can map directly to interfaces in NHibernate, by plugging in an EmptyInterceptor during the configuration stage. The job of this interceptor would be to provide implementations to the interfaces you are defining in your mapping files.
public class ProxyInterceptor : EmptyInterceptor
{
public ProxyInterceptor(ITypeHandler typeHandler) {
// TypeHandler is a custom class that defines all Interface/Poco relationships
// Should be written to match your system
}
// Swaps Interfaces for Implementations
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
var handler = TypeHandler.GetByInterface(clazz);
if (handler == null || !handler.Interface.IsInterface) return base.Instantiate(clazz, entityMode, id);
var poco = handler.Poco;
if (poco == null) return base.Instantiate(clazz, entityMode, id);
// Return Poco for Interface
var instance = FormatterServices.GetUninitializedObject(poco);
SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);
return instance;
}
}
After this, all relationships and mappings can be defined as interfaces.
public Parent : IParent {
public int ID { get; set; }
public string Name { get; set; }
public IChild Child { get; set; }
}
public Child : IChild {
public int ID { get; set; }
public string Name { get; set; }
}
public class ParentMap : ClassMap<IParent>
{
public ParentMap()
{
Id(x => x.ID).GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name)
}
}
...
This type of technique is great if you want to achieve true decoupling of your ORM, placing all configuration/mappings in a seperate project and only referencing interfaces. Your domain layer is then not being polluted with ORM, and you can then replace it at a later stage if you need to.
how are you querying? If you're using HQL you need to import the interface's namespace with an HBM file with this line:
<import class="name.space.ICustomer, Customers" />
If you're using Criteria you should just be able to query for ICustomer and it'll return both customer types.
If you're mapping a class that has a customer on it either through a HasMany, HasManyToMany or References then you need to use the generic form:
References<NiceCustomer>(f=>f.Customer)
If you want it to cope with either, you'll need to make them subclasses
Subclassmap<NiceCustomer>
In which case I think you'll need the base class Customer and use that for the generic type parameter in the outer class:
References<Customer>(f=>f.Customer)
Regardless, you shouldn't change your domain model to cope with this, it should still have an ICustomer on the outer class.
I'm not sure if the 1.0RTM has the Generic form working for References but a quick scan of the changes should show the change, which I think is a two line addition.
It is not possible to map an interface in nhibernate. If your goal is to be able to query using a common type to retrieve both types of customers you can use a polymorphic query. Simply have both your classes implement the interface and map the classes normally. See this reference:
https://www.hibernate.org/hib_docs/nhibernate/html/queryhql.html (section 11.6)

Using Fluent NHibernate Auto Mapping to map IDs of type object from base Entity class

In the project I'm working on now, we have base Entity class that looks like this:
public abstract class Entity<T> where T : Entity<T>
{
public virtual object Id { get; protected set }
// Equals, GetHashCode overrides, etc...
}
Most classes inheriting from Entity should map Id to int column in SQL Server database, but at least one will need to map to long (bigint).
Is it possible to create FluentNH Auto Mapping convention to map those object Ids to int by default? Then we could use another convention or IAutoMappingOverride to handle long Ids.
Thanks!
To answer my own question... It's possible.
You can define convention like this:
internal class PrimaryKeyConvention : IIdConvention
{
public bool Accept(IIdentityPart id)
{
return true;
}
public void Apply(IIdentityPart id)
{
if (<ID should be long>)
id.SetAttribute("type", "Int64");
else
id.SetAttribute("type", "Int32");
}
}