2010-08-04 14 views
6

Ich verwende den Namespace System.ComponentModel.DataAnnotations, um meine Domänenklassen zu validieren. Wie kann ich ein benutzerdefiniertes Attribut erstellen, um die Eindeutigkeit einer Eigenschaft unabhängig von der Datenbank (z. B. über eine Schnittstelle) zu überprüfen?Eindeutige Einschränkung mit Datenannotation

Antwort

1

Wenn ich Sie richtig verstehe, sollten Sie in der Lage sein, ein benutzerdefiniertes ValidationAttribute zu erstellen und über eine benutzerdefinierte Factory einen Kontext zu Ihrem Repository zu erhalten.

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

Sie würden eine IRepository Schnittstelle mit einem IsUnique() -Methode haben. Die MyRepositoryFactory würde eine statische Methode namens Create() haben, die das konkrete Repository für Ihre Datenbank erstellen würde. Wenn sich der Datenbanktyp ändert, müssen Sie nur die Factory aktualisieren, um ein neues Repository für Ihre neue Datenbank zurückzugeben.

8

Dies ist die Lösung, die ich für diese Situation entwickelt habe, es überprüft einfach die Tabelle auf einen Datensatz mit einer anderen ID, die den gleichen Wert für die zu validierende Eigenschaft hat. Es wird davon ausgegangen, dass Sie LinqToSQL verwenden und dass jede Tabelle, für die diese Art von Validierung erforderlich ist, über eine einzige ID-Spalte verfügt.

Ich würde auch eine eindeutige Einschränkung auf die zugrunde liegende Tabelle in der Datenbank setzen. Dieses Attribut ermöglicht es mir, eine nette Fehlermeldung in das Formular einzufügen und es der entsprechenden Eigenschaft zuzuordnen.

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

Beispiel Nutzung:

[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; } 
    } 
} 
0

I Liebe @ DAVEB-Lösung. Leider brauchte ich drei Jahre später eine ziemlich schwere Modifikation für mich. Hier ist seine Lösung für EF6 aktualisiert. Hoffentlich rettet jemand eine Stunde oder 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; 
    } 
} 
1

nur etwas tun, wie dies auf dem Modell

[StringLength(100)] 
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)] 
public string CodigoHabilitacion { get; set; } 
Verwandte Themen