Unique constraint with data annotation

I use the System.ComponentModel.DataAnnotations namespace to validate my domain classes. How to create a custom attribute to check the uniqueness of a property regardless of the database (for example, through some interface)?

+6
validation asp.net-mvc data-annotations
source share
4 answers

This solution, which I came up with for this situation, simply checks the table for writing with a different identifier that has the same value for the property being checked. It is assumed that you will use LinqToSQL and that any table that requires such verification has one identifier column.

I would also set a unique restriction on the base table in the database. This attribute allows me to add a good error message to the form and associate it with the corresponding property.

 public class UniqueAttribute : ValidationAttribute { public Func<DataContext> GetDataContext { get; private set; } public string IDProperty { get; private set; } public string Message { get; private set; } public UniqueAttribute(Type dataContextType, string idProperty, string message) { IDProperty = idProperty; Message = message; GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType); } public UniqueAttribute(Type dataContextType, string idProperty, string message, string connectionString) { IDProperty = idProperty; Message = message; GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType, new object[] { connectionString }); } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var idProperty = validationContext.ObjectType.GetProperty(IDProperty); var idType = idProperty.PropertyType; var id = idProperty.GetValue(validationContext.ObjectInstance, null); // Unsightly hack due to validationContext.MemberName being null :( var memberName = validationContext.ObjectType.GetProperties() .Where(p => p.GetCustomAttributes(false).OfType<DisplayAttribute>().Any(a => a.Name == validationContext.DisplayName)) .Select(p => p.Name) .FirstOrDefault(); if (string.IsNullOrEmpty(memberName)) { memberName = validationContext.DisplayName; } // End of hack var validateeProperty = validationContext.ObjectType.GetProperty(memberName); var validateeType = validateeProperty.PropertyType; var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null); var idParameter = Expression.Constant(id, idType); var validateeParameter = Expression.Constant(validatee, validateeType); var objectParameter = Expression.Parameter(validationContext.ObjectType, "o"); var objectIDProperty = Expression.Property(objectParameter, idProperty); var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty); var idCheck = Expression.NotEqual(objectIDProperty, idParameter); var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter); var compositeCheck = Expression.And(idCheck, validateeCheck); var lambda = Expression.Lambda(compositeCheck, objectParameter); var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2); var genericCountMethod = countMethod.MakeGenericMethod(validationContext.ObjectType); using (var context = GetDataContext()) { var table = context.GetTable(validationContext.ObjectType) as IQueryable<Models.Group>; var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda }); if (count > 0) { return new ValidationResult(Message); } } return null; } } 

Usage example:

 [MetadataType(typeof(UserMetadata))] public partial class Group : IDatabaseRecord { public class UserMetadata { [Required(ErrorMessage = "Name is required")] [StringLength(255, ErrorMessage = "Name must be under 255 characters")] [Unique(typeof(MyDataContext), "GroupID", "Name must be unique")] public string Name { get; set; } } } 
+8
source share

If I understand you correctly, you can create a custom ValidationAttribute attribute and get the context in your repository through a custom factory.

Validator:

 using System.ComponentModel.DataAnnotations; public class DBUniqueAttribute : ValidationAttribute { private IRepository Repository{ get; set;} public DBUniqueAttribute() { this.Repository = MyRepositoryFactory.Create(); } public override bool IsValid(object value) { string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture); return Repository.IsUnique(stringValue); } } 

You will have an IRepository interface using the IsUnique () method. MyRepositoryFactory will have a static Create () method that would create the specific repository needed for your database. If the database type changes, you only need to update Factory to return a new repository for your new database.

+1
source share

just do something similar on your model

 [StringLength(100)] [Index("IX_EntidadCodigoHabilitacion", IsUnique = true)] public string CodigoHabilitacion { get; set; } 
+1
source share

I like @ daveb's solution. Unfortunately, after three years, quite a major change was required for me. Here his solution is updated for EF6. Hope someone saves an hour or so.

 public class UniqueAttribute : ValidationAttribute { public UniqueAttribute(string idProperty, string message) { IdProperty = idProperty; Message = message; } [Inject] public DataContext DataContext { get; set; } private string IdProperty { get; set; } private string Message { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var objectType = validationContext.ObjectType; if (objectType.Namespace == "System.Data.Entity.DynamicProxies") { objectType = objectType.BaseType; } var idProperty = objectType.GetProperty(IdProperty); var idType = idProperty.PropertyType; var id = idProperty.GetValue(validationContext.ObjectInstance, null); var memberName = validationContext.MemberName; var validateeProperty = objectType.GetProperty(memberName); var validateeType = validateeProperty.PropertyType; var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null); var idParameter = Expression.Constant(id, idType); var validateeParameter = Expression.Constant(validatee, validateeType); var objectParameter = Expression.Parameter(objectType, "o"); var objectIdProperty = Expression.Property(objectParameter, idProperty); var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty); var idCheck = Expression.NotEqual(objectIdProperty, idParameter); var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter); var compositeCheck = Expression.And(idCheck, validateeCheck); var lambda = Expression.Lambda(compositeCheck, objectParameter); var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2); var genericCountMethod = countMethod.MakeGenericMethod(objectType); var table = DataContext.Set(objectType); var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda }); if (count > 0) { return new ValidationResult(Message); } return null; } } 
0
source share

All Articles