namespace
Domain.Validators
{
using
System;
using
System.Linq;
using
System.ComponentModel.DataAnnotations;
using
System.Data.Entity;
using
System.Linq.Expressions;
using
System.Collections.Generic;
using
System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public
class
UniqueValueAttribute : ValidationAttribute
{
private
const
string
Where =
"System.Linq.IQueryable`1[TSource] Where[TSource](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Boolean]])"
;
private
const
string
Count =
"Int32 Count[TSource](System.Linq.IQueryable`1[TSource])"
;
private
int
total = 0;
private
Type dbContextType;
private
IEnumerable<
string
> keys;
public
UniqueValueAttribute(Type dbContextType,
params
string
[] keys)
{
if
(!
typeof
(DbContext).IsAssignableFrom(dbContextType))
throw
new
ArgumentException(
"Only dbCOntext derived type could be used as parameter"
);
this
.dbContextType = dbContextType;
this
.keys =
new
List<
string
>(keys);
}
protected
override
ValidationResult IsValid(
object
value, ValidationContext validationContext)
{
var dbContext = (DbContext) Activator.CreateInstance(dbContextType);
var property =
dbContextType
.GetProperties()
.Where(
p =>
(p.PropertyType.IsGenericType) &&
(p.PropertyType.GetGenericTypeDefinition() ==
typeof
(DbSet<>)) &&
(p.PropertyType.GetGenericArguments()[0].IsAssignableFrom(validationContext.ObjectType))
).Single();
IQueryable repository = (IQueryable)property.GetValue(dbContext,
null
);
var propertyType = property.PropertyType.GetGenericArguments()[0];
var queryableWhere = GetQueryableMethod(propertyType, Where);
var queryableCount = GetQueryableMethod(propertyType, Count);
var uniqueMemberInfo = GetMember(propertyType, validationContext.DisplayName);
var paramX = Expression.Parameter(propertyType,
"x"
);
Expression lambda =
Expression.Equal(
Expression.MakeMemberAccess(
paramX,
uniqueMemberInfo),
Expression.Constant(value));
foreach
(var keyPart
in
keys)
{
var keyPartMemberInfo = GetMember(propertyType, keyPart);
object
keyPartValue = propertyType.GetProperty(keyPart).GetValue(validationContext.ObjectInstance,
null
);
lambda =
Expression.AndAlso(
lambda,
Expression.NotEqual(
Expression.MakeMemberAccess(
paramX,
keyPartMemberInfo),
Expression.Constant(keyPartValue)));
}
lambda = Expression.Lambda(lambda, paramX);
var countQuery =
Expression.Call(
queryableCount,
Expression.Call(
queryableWhere,
repository.Expression,
lambda));
total = (
int
)repository.Provider.Execute<
int
>(countQuery);
if
(total == 0)
return
null
;
ErrorMessage = String.Format(
"The value {0} already exists"
, value.ToString());
return
new
ValidationResult(ErrorMessage,
new
[] { validationContext.DisplayName });
}
protected
static
MethodInfo GetQueryableMethod(Type type,
string
signature)
{
return
typeof
(System.Linq.Queryable)
.GetMethods()
.Where(x => x.ToString() == signature)
.Single()
.MakeGenericMethod(type);
}
protected
static
MemberInfo GetMember(Type type,
string
memberName)
{
return
type
.GetMembers()
.Where(x => x.Name == memberName)
.Single();
}
}
}