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.
Related
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?
Given the classes below:
public class Address : Place
{
public virtual string Street { get; set; }
public virtual int Number { get; set; }
public override string WhereAmI
{
get { string.Format("{0} {1}", Street , Number); }
}
}
public abstract class Place : DomainEntity
{
public abstract string WhereAmI { get; }
}
When I use this mapping:
var autoMap = AutoMap.AssemblyOf<Party>()
.Override<Place>(map => map.IgnoreProperty(p => p.WhereAmI))
.Override<Address>(map => map.IgnoreProperty(p => p.WhereAmI))
.Where(type => type.Namespace != null && type.Namespace.Contains("Models"));
I still get the error: Could not find a setter for property 'WhereAmI' in class 'Address'
Things I did:
When i remove the property from the base class "Address" it works.
When i use .OverrideAll(map => map.IgnoreProperty("WhereAmI")) But I don't want it to be global because in another class i might use the same property name where I DO want to include this Property
Is there any way to get this to work other then to use an Interface?
I tried tracking down in the FluentNHibernate code exactly why the IgnoreProperty seems to break down when the property being ignored is coming from a base class, but ran out of time. It seems to work fine if the get-only property is not coming from a base class.
Anyway, the solution to your situation seems to be to create a custom IAutomappingConfiguration by inheriting from DefaultAutomappingConfiguration. See this stack overflow answer: How can I create a Fluent NHibernate Convention that ignores properties that don't have setters.
Here's the custom automapping configuration that I used successfully to automap the example entity you provided:
protected class CustomConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap (Member member)
{
if (member.IsProperty && member.IsPublic && !member.CanWrite)
{
return false;
}
return base.ShouldMap(member);
}
public override bool ShouldMap(Type type)
{
return type.Namespace != null && type.Namespace.Contains("Models");
}
}
And then its use:
var autoMap = AutoMap
.AssemblyOf<DomainEntity>(new CustomConfiguration());
Note that the Where clause in your example had to move into the custom configuration class as its not allowed to be chained if you are using a custom configuration instance.
I have two tables, Locations and Facilities
They map to two classes,
public Location : Entity
{
//properties
}
public Facility : Entity
{
public virtual Location Location { get; set; }
}
Everything works just dandy, until I change facility to this
public Facility : Location
{
}
Now I get an exception from nHibernate saying
NHibernate.ADOException was unhandled by user code
Message=could not execute query
InnerException: System.Data.SqlClient.SqlException
Message=Invalid object name 'Facility'.
For some reason it is not creating the plural name of the table into the sql string.
Thanks for any help!
EDIT
This is my current TableNameConvention
public class TableNameConvention : IClassConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IClassInstance instance)
{
instance.Table(Inflector.Net.Inflector.Pluralize(instance.EntityType.Name));
}
}
When Facility inherits from Entity, the Facility does run through this method. When it inherits from Location, it does not
Edit 2
Figured I'd post everything...
public class AutoPersistenceModelGenerator : IAutoPersistenceModelGenerator
{
#region IAutoPersistenceModelGenerator Members
public AutoPersistenceModel Generate()
{
var mappings = new AutoPersistenceModel();
mappings.AddEntityAssembly(typeof(Person).Assembly).Where(GetAutoMappingFilter);
mappings.Conventions.Setup(GetConventions());
mappings.Setup(GetSetup());
mappings.IgnoreBase<Entity>();
mappings.IgnoreBase(typeof(EntityWithTypedId<>));
mappings.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
return mappings;
}
#endregion
private Action<AutoMappingExpressions> GetSetup()
{
return c =>
{
c.FindIdentity = type => type.Name == "Id";
};
}
private Action<IConventionFinder> GetConventions()
{
return c =>
{
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.ForeignKeyConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.HasManyConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.HasManyToManyConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.ManyToManyTableNameConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.PrimaryKeyConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.ReferenceConvention>();
c.Add<BHP.DEC.Data.NHibernateMaps.Conventions.TableNameConvention>();
};
}
/// <summary>
/// Provides a filter for only including types which inherit from the IEntityWithTypedId interface.
/// </summary>
private bool GetAutoMappingFilter(Type t)
{
return t.GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IEntityWithTypedId<>));
}
}
Have you set a convention?
public class TableNameConvention : IClassConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IClassInstance instance)
{
string typeName = instance.EntityType.Name;
instance.Table(Inflector.Net.Inflector.Pluralize(typeName));
}
}
This is an old question, but for the sake of others who stumble upon this looking for an answer, you can also create a convention that uses the built-in PluralizationService that comes with EF:
public class TableNameConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
string typeName = instance.EntityType.Name;
instance.Table(PluralizationService.CreateService(CultureInfo.CurrentCulture).Pluralize(typeName));
}
}
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));
}
}
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).