Mouthful of a title... So the background story is the need to check uniqueness of a value against the existing database of values during the Enterprise Library (v4.0) validation (and before the DB constraint violation), oh and I'm using NHibernate (v2.0) for persistence. I decided to to create a ValueValidator to allow the application of rule by config or attribute rather than implement as self validation. Straight to it then. I will list the full class later but to help explain the cocky bit these are the main methods:
private bool IsUnique(Type targetType, string propertyName, object propertyValue, object validatedObject)
{
using (ISession session = m_factory.OpenSession())
{
int matches = (int)session.CreateCriteria(targetType)
.Add(Expression.Eq(propertyName, propertyValue))
.Add(Expression.Not(EqualIdentifier(targetType, validatedObject)))
.SetProjection(Projections.ProjectionList()
.Add(Projections.RowCount()))
.UniqueResult();
return (matches == 0);
}
}
private SimpleExpression EqualIdentifier(Type targetType, object validatedObject)
{
ISessionFactoryImplementor factoryImplementor = (ISessionFactoryImplementor)m_factory;
IEntityPersister persister = factoryImplementor.GetEntityPersister(targetType);
object idPropertyValue = persister.GetIdentifier(validatedObject, EntityMode.Poco);
string idPropertyName = persister.IdentifierPropertyName;
return Expression.Eq(idPropertyName, idPropertyValue);
}
The IsUnique method is called within the DoValidate of the ValueValidator, it constructs a criteria query to basically select count(*) from table where unique property = value and not key column = key value - with the identifier detail returned as a SimpleExpression from the EqualIdentifier method using the objects persister. Actually pretty simple and sweet! Full code for the validator:
public class UniqueValueValidator : ValueValidator
{
private ISessionFactory m_factory;
public UniqueValueValidator(string messageTemplate, string tag, bool negated)
: base(messageTemplate, tag, negated)
{
Configuration config = new Configuration().Configure();
m_factory = config.BuildSessionFactory();
}
protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
{
Type targetType = GetTargetType(currentTarget);
object target = GetTargetObject(currentTarget);
if (!IsUnique(targetType, key, objectToValidate, target))
validationResults.AddResult(new ValidationResult(key + " must be unique", currentTarget, key, base.Tag, this));
}
private bool IsUnique(Type targetType, string propertyName, object propertyValue, object validatedObject)
{
using (ISession session = m_factory.OpenSession())
{
int matches = (int)session.CreateCriteria(targetType)
.Add(Expression.Eq(propertyName, propertyValue))
.Add(Expression.Not(EqualIdentifier(targetType, validatedObject)))
.SetProjection(Projections.ProjectionList()
.Add(Projections.RowCount()))
.UniqueResult();
return (matches == 0);
}
}
private SimpleExpression EqualIdentifier(Type targetType, object validatedObject)
{
ISessionFactoryImplementor factoryImplementor = (ISessionFactoryImplementor)m_factory;
IEntityPersister persister = factoryImplementor.GetEntityPersister(targetType);
object idPropertyValue = persister.GetIdentifier(validatedObject, EntityMode.Poco);
string idPropertyName = persister.IdentifierPropertyName;
return Expression.Eq(idPropertyName, idPropertyValue);
}
private Type GetTargetType(object currentTarget)
{
if (currentTarget == null)
throw new ArgumentException("Target should not be null");
if (currentTarget is IValidationIntegrationProxy)
return (currentTarget as IValidationIntegrationProxy).ValidatedType;
return currentTarget.GetType();
}
private object GetTargetObject(object currentTarget)
{
if (currentTarget == null)
throw new ArgumentException("Target should not be null");
if (currentTarget is IValidationIntegrationProxy)
return (currentTarget as IValidationIntegrationProxy).GetRawValue();
return currentTarget;
}
protected override string DefaultNegatedMessageTemplate
{
get { return "The value must be unique"; }
}
protected override string DefaultNonNegatedMessageTemplate
{
get { return "The value must be unique"; }
}
}
and for the attribute:
public class UniqueValueValidatorAttribute : ValueValidatorAttribute
{
protected override Validator DoCreateValidator(Type targetType)
{
return new UniqueValueValidator(MessageTemplate, Tag, Negated);
}
}
Which means that you can add the UniqueValueValidator attribute to properties to enforce unique value during validation:
[UniqueValueValidator]
public virtual string Name { get; set; }