9

I'm trying to make a custom validation [IsUnique]. That check if is property value is unique and return a proper message.

This is my code, but this only work for a specified class, is possible to do a method that get the proper class by the metadata?

public class ArticleMetaData
    {
        [Required(AllowEmptyStrings = false)]
        [IsUnique("Name")]
        public String Name{ get; set; }      
    }

And my custom validation:

class IsUnique : ValidationAttribute
    {
        public IsUnique(string propertyNames)
        {
            this.PropertyNames = propertyNames;
        }

        public string PropertyNames { get; private set; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {

            var myproperty = validationContext.ObjectType.GetProperty(PropertyNames);
            var value = propiedad.GetValue(validationContext.ObjectInstance, null);

            IEnumerable<String> properties;

            List<string> propertiesList = new List<string>();
            propertiesList.Add(myproperty.Name);

            var dba = new myContext();

            if (dba.Articles.Any(article => article.Name == (string)value))
            {
                return new ValidationResult("The name already exist", propertiesList);
            }
            return null;
        }
    }

the idea would be to just use the annotation [isUnique] and the method take the class with annotation and search the corresponding entity.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
grteibo
  • 607
  • 3
  • 11
  • 20
  • 1
    possible duplicate of [Can I store the generics attribute into an object's field](http://stackoverflow.com/questions/304640/can-i-store-the-generics-attribute-into-an-objects-field) – Nate Barbettini Aug 23 '15 at 22:29
  • Are you trying to prevent the user on saving duplicates records in the database? – Juan M. Elosegui Aug 23 '15 at 23:27
  • @JuanM.Elosegui that's my goal. not duplicate names, email, etc. it would be as a unique key SQL – grteibo Aug 23 '15 at 23:30

3 Answers3

7

When writing Validation Attributes, you can use ValidationContext to gain some information about validation such as Name of Property that you are validating, Type of object that you are validating and so on.

So you don't need to declare which property you want to check for uniqueness, or which entity you should check, or event you don't need to retrieve value using reflection, because the value has been passed to IsValid method.

When using DbContext, you canexecute Sql queries, so you can check for uniqueness using sql query simply. It is more simple than try to Create generic linq query on the fly.

May be this idea help you. Here is some changes in your code according to the idea:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var db = new YourDBContext();

    var className = validationContext.ObjectType.Name.Split('.').Last();
    var propertyName = validationContext.MemberName;
    var parameterName = string.Format("@{0}", propertyName);

    var result = db.Database.SqlQuery<int>(
        string.Format("SELECT COUNT(*) FROM {0} WHERE {1}={2}", className, propertyName, parameterName),
        new System.Data.SqlClient.SqlParameter(parameterName, value));
    if (result.ToList()[0] > 0)
    {
        return new ValidationResult(string.Format("The '{0}' already exist", propertyName),
                    new List<string>() { propertyName });
    }

    return null;
}

To use this attribute, simply put [IsUnique] above your property.

[IsUnique]
YourProperty { get; set; }

Then run a test using such code:

var db = new YourDbContext();
db.Configuration.ValidateOnSaveEnabled = true;
db.Categories.Add(new YourEntity() { YourProperty = "DuplicateName" });
db.SaveChanges();

It is a good practice to validate only such aspect of your entity using attributes, that can be validated offline.

Validation Attributes like StringLength, RegularExpression, Required and such validations are examples of good attributes and Validation Attributes that checks for uniqness or other database related rules are examples of inappropriate attributes.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • I'm trying to apply your example. About your last parraph I agree with that, but in some case the system must validate that the user doesnt duplicate names. Thanks for the tips! – grteibo Aug 23 '15 at 23:29
  • @grteibo, you are welcome :) In cases that the system must prevent duplicate data, it is better to do this in your Business Logic codes. There you can create Generic method for checking for duplicate entry and reuse that. The syntax of such method can be: bool IsUnique(string propertyName, object value, YourDbContext db) – Reza Aghaei Aug 23 '15 at 23:52
  • at first I approached the performing validations in the business logic system. But I thought it would be best done through annotations in entities. – grteibo Aug 24 '15 at 00:03
  • well, my table names and my entities names are not the same, so now I'm trying to get the table name from an entitie -.- – grteibo Aug 24 '15 at 00:43
  • 1
    Simply add a property (EntityName) to attribute and use it instead of className if it has value, and set attribute like this: IsUnique(EntityName="EntityName") wherever you need. But I don't edited the code to remain more simple and global as an answer. – Reza Aghaei Aug 24 '15 at 00:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/87727/discussion-between-grteibo-and-user3110834). – grteibo Aug 24 '15 at 01:01
  • @RezaAghaei You're awesome. – Mohammad Chamanpara Aug 25 '15 at 17:02
2

I think the best way is to let the database do its works.

Create a constraint in the database to prevent two articles have the same name (or whatever uniqueness you need). Then, when the user create a new article or update an existing one with an existing article name, the database will throw an exception. Catch that exception and let the user knows about the issue.

Juan M. Elosegui
  • 6,471
  • 5
  • 35
  • 48
0

It would have been nice if there were generic attributes, but such are not supported. However, you can try using the Set method of the DbContext which takes the entity type as a parameter. To query the non-generic DbSet you can use the System.Linq.Dynamic library (you can add it from NuGet). It allows to query the DbSet using string predicates. Here is an example:

var existingEntityQuery = myContext.Set(validationContext.ObjectType)
     .Where("Name= @0", (string)value);
var enumerator = existingEntityQuery.GetEnumerator();

if (enumerator.MoveNext())
{
    var entity = enumerator.Current;

    if (entity != null)
    {
         return new ValidationResult("The name already exist", propertiesList);
    }
}
Panayot Todorov
  • 397
  • 2
  • 11