This post is about one possible answer to the question mememem ask me in this post (ah… nice nick).
The task is create a validator to validate the uniqueness, of an entity instance, using some properties’ values instead its POID (Persistence Object ID).
The implementation
public class BlogPost
{
public string Title { get; set; }
public DateTime Date { get; set; }
public string Content { get; set; }
}
we want check the uniqueness using the value of Title and the value of Date obviously without use both properties as composite-PK; so, we can’t have two posts with the same title in the same day.
Since NHibernate2.1.0GA we can instruct NH about our concept of uniqueness in a very explicit, self documented, and nice way:
<class name="BlogPost">
<id type="guid">
<generator class="guid.comb"/>
</id>
<natural-id mutable="true">
<property name="Title"/>
<property name="Date" type="Date"/>
</natural-id>
<property name="Content" type="string(10000)"/>
</class>
clear no ? we are defining explicitly which is our natural-id, that mean we want a unique key with Title and Date. The result DDL is:
create table BlogPost (
id UNIQUEIDENTIFIER not null,
Title NVARCHAR(255) null,
Date DATETIME null,
Content NVARCHAR(MAX) null,
primary key (id),
unique (Title, Date)
)
We having NHibernate and the DataBase synchronized with the same rule and now we want create a validator to use with NHibernate.Validator to synchronize even our business logic (and prevent ADO.NET exceptions).
First the attribute to “mark” my classes:
[AttributeUsage(AttributeTargets.Class)]
[ValidatorClass(typeof(NaturalIdUniquenessValidator))]
public class NaturalIdUniquenessAttribute : Attribute, IRuleArgs
{
public string Message { get; set; }
}
then the validator implementation with NHibernate integration. In this implementation I’m using the Stateless-Session but you can use, a new statefull session or factory.GetCurrentSessionContext or whatever you have as session-provider. The matter is not from where you get the session but how create the query in a generic way that can be used for any entity without need any kind of “invasion” in my existing implementation.
Note: this is a classic usage of the stateless-session but take care with the BestGuessEntityName method.
public class NaturalIdUniquenessValidator : IValidator
{
private readonly ISessionFactory factory;
public NaturalIdUniquenessValidator(ISessionFactory sessionFactory)
{
factory = sessionFactory;
}
public bool IsValid(object value, IConstraintValidatorContext constraintContext)
{
if (ReferenceEquals(value, null))
{
return true;
}
using (var session = factory.OpenStatelessSession())
{
var entityName = ((ISessionImplementor) session).BestGuessEntityName(value);
var meta = factory.GetClassMetadata(entityName);
if (ReferenceEquals(meta, null) || !meta.HasNaturalIdentifier)
{
return true;
}
var propsValues = meta.GetPropertyValues(value, EntityMode.Poco);
var propsNames = meta.PropertyNames;
var criteria = session.CreateCriteria(entityName);
foreach (var propIdx in meta.NaturalIdentifierProperties)
{
criteria.Add(Restrictions.Eq(propsNames[propIdx], propsValues[propIdx]));
}
return criteria.UniqueResult() == null;
}
}
}
In the implementation I’m using some NHibernate advanced information to create a ICriteria with the properties defined as natural-id; the implementation is working with any entity of your domain. The strange thing, there, is another: the validator does not have a default constructor… where is the trick ?
Well… first the implementation with an home-made-2-minutes-IoC:
public class ConstraintValidatorFactory : DefaultConstraintValidatorFactory
{
public override IValidator GetInstance(Type type)
{
var persistenceCtor = type.GetConstructor(new[] {typeof (ISessionFactory)});
if(persistenceCtor != null)
{
return (IValidator)
persistenceCtor.Invoke(new object[] { NHibernateStaticContainer.Factory });
}
return base.GetInstance(type);
}
}
The IConstraintValidatorFactory is the factory for classes implementing IValidator and you can inject the concrete implementation through NHV configuration.
var nhvConfig = new FluentConfiguration();
nhvConfig.SetConstraintValidatorFactory<ConstraintValidatorFactory>();
Another, more generic, implementation of the IConstraintValidatorFactory could be:
public class ServiceLocatorValidatorFactory : DefaultConstraintValidatorFactory
{
#region Implementation of IConstraintValidatorFactory
public override IValidator GetInstance(Type type)
{
ConstructorInfo defaultConstructorInfo = type.GetConstructor(new Type[0]);
if (defaultConstructorInfo == null)
{
return (IValidator) ServiceLocator.Current.GetInstance(type);
}
return base.GetInstance(type);
}
#endregion
}
here you can use your preferred IoC framework as the factory/container of validators instances (take care with statefull validators).
The usage
Well… it is completely transparent, you can use this entity-validator as you are using any other.
the definition
public class BlogPostValidation: ValidationDef<BlogPost>
{
public BlogPostValidation()
{
ValidateInstance.Using(new NaturalIdUniquenessAttribute());
}
}
a simple test
var blogPost = new BlogPost
{
Title = "something new",
Date = DateTime.Today,
Content = "there is something new to say."
};
validatorEngine.IsValid(blogPost).Should().Be.True();
using (var s = sessionFactory.OpenSession())
using (var tx = s.BeginTransaction())
{
s.Save(blogPost);
tx.Commit();
}
var otherBlogPost = new BlogPost
{
Title = "something new",
Date = DateTime.Today,
Content = "bha?!?."
};
validatorEngine.IsValid(otherBlogPost).Should().Be.False();
otherBlogPost.Title = "very new";
validatorEngine.IsValid(otherBlogPost).Should().Be.True();
Conclusion
The implementation of the entity BlogPost was NEVER TOUCHED (I did it my way)… now :
- go to your system
- copy&paste the implementation of NaturalIdUniquenessAttribute and NaturalIdUniquenessValidator
- change the implementation of the ConstraintValidatorFactory according to your system
- change the mapping defining your natural-id in the entities where needed
- Enjoy NHibernate.Validator, its flexibility, its injectability and its easy integration with NHibernate.
Tomorrow, perhaps, the no synchronized implementation.
Just out of curiosity. Is there a way to define the order in which validators will be executed and as a result the error messages will be returned?
ReplyDeleteThanks.
Can you elaborate an example of what you need ?
ReplyDeleteSure.
ReplyDeleteLet's have your BlogPost class below for example. It has Title, Date, Comment properties. Let's say all of them have NotNull validation. What I need is to ask NHibernate.Validator to validate my object values using the following order: Date, Comment, Title. And then when I ask it to validate and return error messages, I would like to have those messages in the same order. Do you see my point?
If you familiar with Castle.Components.Validator they have ExecutionOrder property for their validators. I wonder if there's anything similar here? :)
Which is the advantage or which should be the target of an ordered evaluation of constraints ?
ReplyDeleteMy main target is to be able to handle the order in which validation errors are returned when I call ValidatorEngine.Validate(entity).
ReplyDeleteI'll try to explain further. Let's imagine we have a user form with the following inputs:
* Date
* Title
* Comment
If I fill in nothing I will see validation summary like below:
* Date cannot be null.
* Title cannot be null.
* Comment cannot be null.
Or something similar, right? The main idea is to have the validation summary error messages displayed in the same order the inputs are.
Now I might want to have the inputs displayed in another order, e.g. have Title above Date:
* Title
* Date
* Comment
And as a result I want my users to see the validation summary error messages in this order.
Please let me know if that makes sense or there's any other way to do that.
In the InvalidValue you having what you need to create an orderd message base on the order in the UI (using Linq it is a code-line).
ReplyDeleteI'm not sure I've caught your idea.
ReplyDeleteI would appreciate if you could show me the code-line that will allow me to specify the order of error messages using Linq.
However, the example above is just an example of an advantage of using ordered validation. There may be other cases when it is needed. In more complex example, I may have several custom validators which do some logging when being executed, and it is important for me that one of them logs info (or does any other action) before another one. Wouldn't that be useful to have ability to perform ordered validation in this case?
If you use the entity validation event listener integration, you have a chicken & egg problem. Can't build a session factory without the listener, which requires the validators to be built. Can't build the validators without a session factory.
ReplyDelete