How do I override a convention's cascade rule in fluent nhibernate - fluent-nhibernate

I have two classes
public class Document
{
public virtual int Id { get; set; }
public virtual IList<File> Files { get; set; }
}
public class File
{
public virtual int Id { get; protected set; }
public virtual Document Document { get; set; }
}
with the following convention:
public class HasManyConvention : IHasManyConvention
{
public bool Accept(IOneToManyPart target)
{
return true;
}
public void Apply(IOneToManyPart target)
{
target.Cascade.All();
}
}
and these mapping overrides
public class DocumentMappingOverride : IAutoMappingOverride<Document>
{
public void Override(AutoMap<Document> mapping)
{
mapping.HasMany(x => x.Files)
.Inverse()
// this line has no effect
.Cascade.AllDeleteOrphan();
}
}
public class FileMappingOverride : IAutoMappingOverride<File>
{
public void Override(AutoMap<File> mapping)
{
mapping.References(x => x.Document).Not.Nullable();
}
}
I understand that I need to make an IClassConvention for Document to
change the cascade behaviour, however I can't get this to work!
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.SetAttribute("cascade", "all-delete-orphan");
}
}
I get: "The 'cascade' attribute is not declared."
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.HasMany<Document, File>(x => x.Files)
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
Then I get:
"Duplicate collection role mapping Document.Files"
so i added:
mapping.IgnoreProperty(x => x.Files);
to my document mapping, but then Files is always empty.
What am I doing wrong?
How can I override the cascade rule for a single HasMany relationship?
Thanks
Andrew
P.s. Sorry for the cross post with this but I need to get this solved asap.

I know this was forever ago (in computer time) and you might have already solved this. In case you haven't or someone else with a similar question sees this, here goes:
I think you need to create a class that implements IHasManyConvention. IClassConvention modifies an IClassMap (the <class> element) target. cascade is not a valid attribute for <class> so that accounts for the first error. On your second attempt, you were re-mapping the collection, resulting in the "duplicate collection" error.
IHasManyConvention targets an IOneToManyPart, upon which you should be able to call Cascade.AllDeleteOrphan() or just SetAttribute("cascade", "all-delete-orphan") if the former didn't work for some reason.
EDIT
Sorry, I missed that you already had a IHasManyConvention. Since you want to override your convention for just one type, you should just change the Accept method on your convention for that type. Instead of return true;, pull in what you had on your DocumentConvention:
return target.EntityType == typeof(Document);
I believe that OneToManyPart.EntityType references the containing entity type (i.e. Document).

Related

SaveOrUpdate is trying to insert NULL into primary key field

I'm creating a little application using Sharp Architecture, and I've run into a bug I can't figure out. I think it has something to do with the NHibernte mappings. In my HttpPost Create() method, my SaveOrUpdate call is trying to insert null into the table's primary key field. The declaration for the primary key in my model is public virtual int Id { get; protected set; }.
I checked newSprint.Id and it's zero. I think the problem's with my NHibernate Mappings, so I've included all of that below.
Here's the automapping configuration:
public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(System.Type type)
{
return type.GetInterfaces().Any(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntityWithTypedId<>));
}
public override bool ShouldMap(Member member)
{
return base.ShouldMap(member) && member.CanWrite;
}
public override bool AbstractClassIsLayerSupertype(System.Type type)
{
return type == typeof(EntityWithTypedId<>) || type == typeof(Entity);
}
public override bool IsId(Member member)
{
return member.Name == "Id";
}
}
The auto-persistence model generator:
public class AutoPersistenceModelGenerator : IAutoPersistenceModelGenerator
{
public AutoPersistenceModel Generate()
{
var mappings = AutoMap.AssemblyOf<Sprint>(new AutomappingConfiguration());
mappings.IgnoreBase<Entity>();
mappings.IgnoreBase(typeof(EntityWithTypedId<>));
mappings.Conventions.Setup(GetConventions());
mappings.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
return mappings;
}
private static Action<IConventionFinder> GetConventions()
{
return c =>
{
c.Add<PrimaryKeyConvention>();
c.Add<CustomForeignKeyConvention>();
c.Add<HasManyConvention>();
c.Add<TableNameConvention>();
};
}
Thanks in advance for any help anyone can offer.
Edit
I figured out that the problem was with the Table Name Convention. Removing that from the AutoMapping configuration solved the problem. I've removed the extraneous code and added the TableNameConvention mapping in the hopes that someone can explain what specifically about it caused those problems.
public class TableNameConvention : IClassConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IClassInstance instance)
{
instance.Table(Inflector.Net.Inflector.Pluralize(instance.EntityType.Name));
}
}
Your IdGenerator is probably not right. You need to set an appropriate generator for your PrimaryKey. For example, Identity if you're using SQL Server.
I'm not sure how you set this with your setup, not very familiar with Fluent NH. My guess is PrimaryKeyConvention?

Fluent NHibernate - automapping: allow null for single properties

I know this question has been raised in similar form multiple times, but none of the threads could give me the concrete answer to my question.
I use Fluent NHibernate and Fluent`s auto-mapping to map my domain entities. Right now, I use this convention class to set all properties NOT NULL:
public class NotNullColumnConvention : IPropertyConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
{
instance.Not.Nullable();
}
}
The big question is:
What do I need to do, to allow single properties of my entity classes to be NULL?
Here is one of my entity classes:
public class Employee : Entity
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
I´d be really pleased, if someone can finally help me out! All possible search string I have entered into Google return pages, marked as already visited...
Thanks,
Arne
EDIT: Changed title ... Want to allow NULL for single properties
Create an attribute :
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CanBeNullAttribute : Attribute
{
}
And a convention :
public class CanBeNullPropertyConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(
x => !this.IsNullableProperty(x)
|| x.Property.MemberInfo.GetCustomAttributes(typeof(CanBeNullAttribute), true).Length > 0);
}
public void Apply(IPropertyInstance instance)
{
instance.Nullable();
}
private bool IsNullableProperty(IExposedThroughPropertyInspector target)
{
var type = target.Property.PropertyType;
return type.Equals(typeof(string)) || (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
}
}
Drop the attribute on top of your properties.

Fluent Nhibernate Automap convention for not-null field

Could some one help, how would I instruct automap to have not-null for
a column?
public class Paper : Entity
{
public Paper() { }
[DomainSignature]
[NotNull, NotEmpty]
public virtual string ReferenceNumber { get; set; }
[NotNull]
public virtual Int32 SessionWeek { get; set; }
}
But I am getting the following:
<column name="SessionWeek"/>
I know it can be done using fluent-map. but i would like to know it in
auto-mapping way.
Thank you. Also, for reference properties ReferenceConvention need to be done. This is the code that works:
public class ColumnNullConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
if (instance.Property.MemberInfo.IsDefined(typeof(NotNullAttribute), false))
instance.Not.Nullable();
}
} public class ReferenceConvention : IReferenceConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance)
{
instance.Column(instance.Property.Name + "Fk");
if (instance.Property.MemberInfo.IsDefined(typeof(NotNullAttribute), false))
instance.Not.Nullable();
}
}
Here is the way I do it, basically taken from the link you see in the code. There are some other useful conventions there as well
HTH,
Berryl
/// <summary>
/// If nullability for the column has not been specified explicitly to allow NULL, then set to “NOT NULL”.
/// </summary>
/// <remarks>see http://marcinobel.com/index.php/fluent-nhibernate-conventions-examples/</remarks>
public class ColumnNullabilityConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Nullable, Is.Not.Set);
}
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
}
If you are mostly happy with Automapping results but occasionally need to override it for say a couple of properties in a class I find implementing a IAutoMappingOverride for that class the easiest way to achieve that:
public class UserMappingOverride : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Map(x => x.UserName).Column("User").Length(100).Not.Nullable();
}
}
And then use them like this:
AutoMap.AssemblyOf<User>().UseOverridesFromAssemblyOf<UserMappingOverride>();
Similar to ClassMaps - but you don't need to describe every field in the class.
This approach is very similar to the Entity Framework's Code First Fluent API way.
public class Paper Map : IAutoMappingOverride<Paper >
{
public void Override(AutoMapping<Paper> mapping)
{
mapping.Map(x => x.ReferenceNumber).Not.Nullable();
}
}
Int32 is not nullable type by default. Int32? is nullable, so you make it non-nullable just by specifying it as Int32.
You can use conventions to do this automatically. I am not sure which convention to use, but have a look at FluentNHibernate.Conventions.Instances to find the right one. It'll look like this.
public class ColumnConvention : IColumnConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.ColumnInstance instance)
{
if (instance.EntityType.IsDefined(typeof(NotNullAttribute), false))
instance.NotNull = true;
}
public void Apply(FluentNHibernate.Conventions.Instances.IColumnInstance instance)
{
return;
}
}
Just add this convention to your automapping.
I find more often than not, my columns are not null, so I prefer make this convention and only specify columns as nullable:
/// <summary>
/// Indicates that a column should allow nulls
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class NullableAttribute : Attribute
{
}
public class ColumnIsNotNullByDefaultConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(c => !c.Property.MemberInfo.IsDefined(typeof(NullableAttribute), false));
}
}

Getting error "Association references unmapped class" when using interfaces in model

I'm trying to use the automap functionality in fluent to generate a
DDL for the following model and program, but somehow I keep getting
the error "Association references unmapped class: IRole" when I call
the GenerateSchemaCreationScript method in NHibernate. When I replace
the type of the ILists with the implementation of the interfaces (User
and Role) everything works fine. What am I doing wrong here? How can I
make fluent use the implemented versions of IUser and IRole as defined
in Unity?
public interface IRole
{
string Title { get; set; }
IList<IUser> Users { get; set; }
}
public interface IUser
{
string Email { get; set; }
IList<IRole> Roles { get; set; }
}
public class Role : IRole
{
public virtual string Title { get; set; }
public virtual IList<IUser> Users { get; set; }
}
public class User : IUser
{
public virtual string Email { get; set; }
public virtual IList<IRole> Roles { get; set; }
}
I use the following program to generate the DDL using the
GenerateSchemaCreationScript in NHibernate:
class Program
{
static void Main(string[] args)
{
var ddl = new NHibernateSessionManager();
ddl.BuildConfiguration();
}
}
public class NHibernateSessionManager
{
private ISessionFactory _sessionFactory;
private static IUnityContainer _container;
private static void InitContainer()
{
_container = new UnityContainer();
_container.RegisterType(typeof(IUser), typeof(User));
_container.RegisterType(typeof(IRole), typeof(Role));
}
public ISessionFactory BuildConfiguration()
{
InitContainer();
return
Fluently.Configure().Database(MsSqlConfiguration.MsSql2008
.ConnectionString("ConnectionString"))
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<IUser>()))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private void BuildSchema(Configuration cfg)
{
var ddl = cfg.GenerateSchemaCreationScript(new
NHibernate.Dialect.MsSql2008Dialect());
System.IO.File.WriteAllLines("Filename", ddl);
}
}
I am in the same situation as you. Having used the ClassMap before I know you can do this with Fluent but I had never used the AutoMapping feature before. I have successfully been able to do a one to one mapping with the AutoMapper using an IReferenceConvention (see previous SO post).
I have now hit the same problem as you where I have a one to many mapping which I am now having a problem with. There is an IHasManyConvention interface which I have started to look at but have had no luck as of yet.
Just because some thing is hard to do it doesn't make it wrong, mapping to interfaces defiantly has value and can easily be done in the raw nHibernate mapping files or by using Fluents ClassMap mapping files. I think once people start do more with AutoMapping feature there will be more blog posts.
EDIT
I have found an interim solution using an IAutoMappingOverride. Below is a rough example of what you need.
public class RoleAutoMappingOverride : IAutoMappingOverride<Role>
{
public void Override(AutoMapping<Role> mapping)
{
mapping.HasMany<User>( x => x.Users ).KeyColumn( "User_id" );
}
}
EDIT
A college of mine has worked out a better solution that uses conventions instead of the override. This covers how to do a single class but if you look at the SO post I mentioned before you can see how this could be made generic.
public class Foo : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
if (instance.ChildType == typeof(Role))
{
instance.Relationship.CustomClass<User>();
}
}
}
EDIT
I have now turned this and my other post into a blog post:
http://bronumski.blogspot.com/2011/01/making-fluent-nhibernate-automapper.html
You can't provide an interface as the type T in AssemblyOf<T>, you need to provide a concrete type. Or you could use the method that accepts an assemply:
.Mappings(m => m.AutoMappings.Add(
AutoMap.Assembly(myAssembly)))
Edit: The problem is that your classes contain collections of interface types instead of class type. I don't know if it's possible to automap interfaces in this manner. Also, I think there's rarely any value in using interfaces to specify domain objects.

Cascade Saves with Fluent NHibernate AutoMapping

How do I "turn on" cascading saves using AutoMap Persistence Model with Fluent NHibernate?
As in:
I Save the Person and the Arm should also be saved. Currently I get
"object references an unsaved transient instance - save the transient instance before flushing"
public class Person : DomainEntity
{
public virtual Arm LeftArm { get; set; }
}
public class Arm : DomainEntity
{
public virtual int Size { get; set; }
}
I found an article on this topic, but it seems to be outdated.
This works with the new configuration bits. For more information, see http://fluentnhibernate.wikia.com/wiki/Converting_to_new_style_conventions
//hanging off of AutoPersistenceModel
.ConventionDiscovery.AddFromAssemblyOf<CascadeAll>()
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public bool Accept( IOneToOnePart target )
{
return true;
}
public void Apply( IOneToOnePart target )
{
target.Cascade.All();
}
public bool Accept( IOneToManyPart target )
{
return true;
}
public void Apply( IOneToManyPart target )
{
target.Cascade.All();
}
public bool Accept( IManyToOnePart target )
{
return true;
}
public void Apply( IManyToOnePart target )
{
target.Cascade.All();
}
}
Updated for use with the the current version:
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public void Apply(IOneToOneInstance instance)
{
instance.Cascade.All();
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.All();
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.All();
}
}
The easiest way I've found to do this for a whole project is to use DefaultCascade:
.Conventions.Add( DefaultCascade.All() );
Go to "The Simplest Conventions" section on the wiki, for this, and a list of others.
Here's the list from the Wiki:
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
A word of warning - some of the method names in the Wiki may be wrong. I edited the Wiki with what I could verify (i.e. DefaultCascade and DefaultLazy), but can't vouch for the rest. But you should be able to figure out the proper names with Intellisense if the need arises.
The Convention Method Signatures have changed. For the new answer that does exactly what this question asks see THIS QUESTION.
You can also make cascading the default convention for all types. For example (using the article you linked to as a starting point):
autoMappings.WithConvention(c =>
{
// our conventions
c.OneToOneConvention = o => o.Cascade.All();
c.OneToManyConvention = o => o.Cascade.All();
c.ManyToOneConvention = o => o.Cascade.All();
});