Temporarily turn off identity column with Fluent AutoMap? - nhibernate

I have begun to test Fluent NHibernate in C#
I have a well normalized object structure with 20 related classes.
I currently use Fluent 1.3 with NHibernate 3.2.
So far I have managed to use the AutoMap feature which suits me fine,
Very convenient!
BUT ...
3 of the tables are "enum tables" that need to have their records set with specific Id value.
I tried to make manual mappings of these tables and let the rest be automapped.
But when the manual table is created it fails because it references a table that is automapped (and not available for manual mapper?)
Is it possible to use AutoMapping but for some very few classes override identity creation on primary key?
I tried to make a custom convention but without success.
public class OverrideIdentityGeneration : Attribute
{
}
public class ConventionIdentity : AttributePropertyConvention<OverrideIdentityGeneration>
{
protected override void Apply(OverrideIdentityGeneration attribute, IPropertyInstance instance)
{
instance.Generated.Never();
}
}
Is there some other way?
It would be sad to be forced back to use manual mapping for all classes ....

class MyIdConvention : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
if (instance.EntityType == ...)
{
instance.GeneratedBy.Assigned();
}
}
}
Update:
for enum-like classes it's often easier to define an enum as id
class ConfigValue
{
public virtual Config Id { get; set; }
}
// the convention is easy
if (instance.EntityType.IsEnum)
{
instance.GeneratedBy.Assigned();
// to save as int and not string
instance.CustomType(typeof(Config));
}
// querying without magic int values
var configValue = Session.Get<ConfigValue>(Config.UIColor);

I used the idea given by Fifo and extended it to use a custom attribute instead.
To make code readable and avoid redundance when using similar idea in other conventions I added an extension method to check for custom attribute.
This is the code I ended up with:
/// <summary>
/// Convention to instruct FluentNHIbernate to NOT generate identity columns
/// when custom attribute is set.
/// </summary>
public class ConventionIdentity : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
if(instance.CustomAttributeIsSet<NoIdentity>())
instance.GeneratedBy.Assigned();
}
}
/// <summary>
/// Custom attribute definition.
/// </summary>
public class NoIdentity : Attribute
{
}
/// <summary>
/// Example on how to set attribute.
/// </summary>
public class Category
{
[NoIdentity]
public int Id { get; set; }
public string Name { get; set; }
}
public static class IInspectorExtender
{
/// <summary>
/// Extender to make convention usage easier.
/// </summary>
public static T GetCustomAttribute<T>(this IInspector instance)
{
var memberInfos = instance.EntityType.GetMember(instance.StringIdentifierForModel);
if(memberInfos.Length > 0)
{
var customAttributes = memberInfos[0].GetCustomAttributes(false);
return customAttributes.OfType<T>().FirstOrDefault();
}
return default(T);
}
}

Related

Akavache not storing/returning a NodaTime LocalDateTime

I need to store a NodaTime LocalDateTime in an Akavache cache.
I've created a simple app which takes the following class and stores/retrieves it in/from an Akavache cache:
public class TestModel
{
public string Name { get; set; }
public LocalDateTime StartDateTimeLocal {get; set;}
public DateTime StartDateTimeUtc {get;set;}
}
When this is stored in and retrieved from the cache, the StartDateTimeLocal property hasn't been populated.
It seems that Akavache isn't aware of how to serialise/deserialize a LocalDateTime.
Is it possible to register types with Akavache or supply a custom serialisation for unknown types?
Console application to demonstrate it:
using Akavache;
using NodaTime;
using System;
using System.Reactive.Linq;
namespace AkavacheNodaTimeCore
{
class Program
{
static TestModel BeforeModel;
static TestModel AfterModel;
static void Main(string[] args)
{
// Note that we're using Akavache 6.0.27, to match the version we're using in our live system.
BlobCache.ApplicationName = "AkavacheNodaTimeCore";
BlobCache.EnsureInitialized();
BeforeModel = new TestModel()
{
StartLocalDateTime = LocalDateTime.FromDateTime(DateTime.Now),
StartDateTime = DateTime.UtcNow,
};
Console.WriteLine($"Before:LocalDateTime='{BeforeModel.StartLocalDateTime}' DateTime='{BeforeModel.StartDateTime}'");
CycleTheModels();
Console.WriteLine($"After: LocalDateTime='{AfterModel.StartLocalDateTime}' DateTime='{AfterModel.StartDateTime}'");
Console.WriteLine("Note that Akavache retrieves DateTimes as DateTimeKind.Local, so DateTime before and after above will differ.");
Console.WriteLine("Press any key to continue.");
var y = Console.ReadKey();
}
/// <summary>
/// Puts a model into Akavache and retrieves a new one so we can compare.
/// </summary>
static async void CycleTheModels()
{
await BlobCache.InMemory.Invalidate("model");
await BlobCache.InMemory.InsertObject("model", BeforeModel);
AfterModel = await BlobCache.InMemory.GetObject<TestModel>("model");
}
}
}
TestModel class:
using NodaTime;
using System;
namespace AkavacheNodaTimeCore
{
public class TestModel
{
public string Name { get; set; }
public LocalDateTime StartLocalDateTime { get; set; }
public DateTime StartDateTime {get;set;}
}
}
I have added a Git repo with the above in a console application which demonstrates the problem.
You need to configure the JsonSerializerSettings that Akavache uses with Json.NET. You'll need a reference to NodaTime.Serialization.JsonNet, at which point you can create a serializer settings instance, configure it for Noda Time, then add that as a dependency in Splat (which Akavache uses). I haven't used Splat before, so it's possible that this isn't the right way of doing it, but it works with your example:
using Newtonsoft.Json;
using NodaTime.Serialization.JsonNet;
using Splat;
...
// This should be before any of your other code.
var settings = new JsonSerializerSettings();
settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
Locator.CurrentMutable.RegisterConstant(settings, typeof(JsonSerializerSettings));
It may be worth filing in issue in the Akavache repo to request more documentation for customization of serialization settings - the above works, but was guesswork and a little bit of source code investigation.

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

Designing Business Objects to indicate constraints such as Max Length

Is there a standard convention when designing business objects for providing consumers with a way to discover constraints such as a property's maximum length?
It could be used up in the UI layer to, for example, set a Textbox's MaxLength property according to the maximum length limit back in the business object.
Is there a standard design approach for this?
Validation frameworks often contain parts for integrating with UI technologies in communicating the errors. Microsoft Enterprise Library Validation Application Block for instance contains a ValidationProvider extender control for WinForms that binds with the WinForms ErrorProvider control.
Your wish is different though. You want to communicate the constraints before they turn in to errors. Because this is not a standard requirement, I don't believe most validation frameworks have something for this out of the box. However, depending on the chosen framework creating this might be achievable. The Validation Application Block for instance, allows you to analyze the rules that you have registered / configured on a entity. So it is possible to build a control that will do this for you.
[Edit]
What you could also do is validate a form immediately upon startup and after each keystroke. This causes error icons or messages to show up immediately, which allows users to directly see what the constraints are (when you use icons, the user can hover an icon to see the error message). This isn't perhaps as nice as creating your own control, but it much easier to implement.
I have my own validation framework that lets me validate each field with the help of designated ValidationAttribute. It uses Attributes to automate most of the validations.
A sample business object would look like this in my application.
Each business object would inherit from EntityBase abstract class that has a public method called "Validate()". When this method is called on the given instance of the business object it will iterate through all properties of its own having Attributes that are derived from ValidationAttribute can call ValidationAttriubte's IsValid method to validate the value of associated proerty and return true/false with err. msg if any.
User.cs
[TableMapping("Users")]
public class User : EntityBase
{
#region Constructor(s)
public AppUser()
{
BookCollection = new BookCollection();
}
#endregion
#region Properties
#region Default Properties - Direct Field Mapping using DataFieldMappingAttribute
private System.Int32 _UserId;
private System.String _FirstName;
private System.String _LastName;
private System.String _UserName;
private System.Boolean _IsActive;
[DataFieldMapping("UserID")]
[DataObjectFieldAttribute(true, true, false)]
[NotNullOrEmpty(Message = "UserID From Users Table Is Required.")] // VALIDATION ATTRIBUTE
public override int Id
{
get
{
return _UserId;
}
set
{
_UserId = value;
}
}
[DataFieldMapping("UserName")]
[Searchable]
[NotNullOrEmpty(Message = "Username Is Required.")] // VALIDATION ATTRIBUTE
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
[DataFieldMapping("FirstName")]
[Searchable]
public string FirstName
{
get
{
return _FirstName;
}
set
{
_FirstName = value;
}
}
[DataFieldMapping("LastName")]
[Searchable]
public string LastName
{
get
{
return _LastName;
}
set
{
_LastName = value;
}
}
[DataFieldMapping("IsActive")]
public bool IsActive
{
get
{
return _IsActive;
}
set
{
_IsActive = value;
}
}
#region One-To-Many Mappings
public BookCollection Books { get; set; }
#endregion
#region Derived Properties
public string FullName { get { return this.FirstName + " " + this.LastName; } }
#endregion
#endregion
public override bool Validate()
{
bool baseValid = base.Validate();
bool localValid = Books.Validate();
return baseValid && localValid;
}
}
BookCollection.cs
/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection()
{
}
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection (IList<Book> initialList)
: base(initialList)
{
}
}
Custom Attributes might serve your need.

Excluding some tables from Fluent Nhibernate schema Generation

I have some existing asp.net membership and roles tables in a legacy db and I am mapping them to new entities with Fluent Nhibernate.
I also generate the schema directly from Fluent Nhibernate and I then manually tweak the generated sql script to exclude the existing tables.
Is it possible to say to Fluent Nhibernate to exclude from generation certain tables?
SchemaAction.None() in your ClassMap.
Another option would be to create an attribute, say
public class DoNotAutoPersistAttribute : Attribute
{
}
Then in AutoPersistenceModelGenerator you could check for this attribute in the Where clause of AddEntityAssembly.
I've managed this with an attribute + convention:
public enum SchemaAction
{
None
}
[Serializable]
[AttributeUsage(AttributeTargets.Class)]
public class SchemaActionAttribute : Attribute
{
private readonly SchemaAction schemaAction = SchemaAction.None;
public SchemaActionAttribute()
{
}
public SchemaActionAttribute(SchemaAction schemaAction)
{
this.schemaAction = schemaAction;
}
public SchemaAction GetSchemaAction()
{
return schemaAction;
}
}
/// <summary>
/// overrides the default action for entities when creating/updating the schema
/// based on the class having a Schema attribute (<see cref="SchemaActionAttribute" />)
/// </summary>
public class SchemaActionConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
object[] attributes = instance.EntityType.GetCustomAttributes(true);
foreach (object t in attributes)
{
if (t is SchemaActionAttribute)
{
var a = (SchemaActionAttribute) t;
switch(a.GetSchemaAction())
{
case SchemaAction.None:
instance.SchemaAction.None();
return;
default: throw new ApplicationException("That schema action:" + a.GetSchemaAction().ToString() + " is not currently implemented.");
}
}
}
}
}
...
[SchemaAction(SchemaAction.None)]
public class TextItem : Entity
...