1

How to set custom validation that when Entry.state == EntityState.Added is true the below code would work! Custom Validation Code:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class UniqueEmailAddress : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        this.email = (string)value;
        using (G9EntityDataBaseClass oEntity = new G9EntityDataBaseClass())
        {
            if (oEntity.User.Where(u => u.email == email).Count() == 0)
            {
                return ValidationResult.Success;
            }
            else
            {
                return new ValidationResult(ErrorMessageString);
            }
        }
    }
}

use :

[CustomValidation.UniqueEmailAddress]
public string email { set; get; }

i need ((NOT)EntityState.Added):

if (Entry.state != EntityState.Added){
     return ValidationResult.Success;
}

From where should i bring Entry.state

Iman Kari
  • 11
  • 3

3 Answers3

2

A method that works well for validation is implementing IValidatableObject in your entity class:

public class User: IValidatableObject

In the Validate method you can gain access to the context to which the entity object is attached at that moment by adding it to the ValidationContext. So your validation could look like:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    var context = (G9EntityDataBaseClass)validationContext.Items["Context"];
    if (context.User
               .Where(u => u.email == email && u.UserID != this.UserID)
               .Any())
    {
        return new ValidationResult(ErrorMessageString);
    }
    else
    {
        return ValidationResult.Success;
    }
}

You add the context to the validation context by overriding this method in your DbContext class:

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
                       IDictionary<object, object> items)
{
    items.Add("Context", this);
    return base.ValidateEntity(entityEntry, items);
}

The logic is simple: validation always fails if there is an identical email that belongs to another user (u.UserID != this.UserID). This applies both to inserting new users (ID = 0) and existing users.

By the way, it's debatable whether you should actually use the context here to access the database, but the alternative, overriding DbContext.ValidateEntity doesn't appeal to me either, because this causes long strands of code full of type casts of which only one is necessary each time. And this code is traversed for each entity that's validated. The Validate method id only executed when its applicable.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Ok so I overwrote that method in my DbContext, still doesn't work. I set a breakpoint inside of it and it never gets hit so so the Items property of validationContext is still zero. Reviewed everything and didn't get to what's wrong... any ideas? – Felipe Correa Aug 19 '15 at 16:42
  • I found out that you had to call `GetValidationErrors` on the `DbContext` manually so that it makes the validations as told in this [link.](https://msdn.microsoft.com/en-us/data/gg193959.aspx) I discarded this option because of that, I don't want to need that extra code in almost every action. I will go with Custom Validation Attributes. – Felipe Correa Aug 19 '15 at 21:35
  • 2
    @FelipeCorrea A context will validate its entities on `SaveChanges` by default. – Gert Arnold Aug 19 '15 at 21:37
  • Then I don't know what's wrong in my case because the `Validate` method of my entity is called and the `DbContext.ValidateEntity` is never called. That causes your recommendation to fail because I don't have any keys in the `items`dictionary. – Felipe Correa Aug 19 '15 at 21:41
  • 1
    I realized what is going on. In my action when `Model.IsValid` is called it inmediatly fires the entity `Validate` method (the context hasn't been added to the `items` dictionary). Then when the code reaches the call to `db.SaveChanges` the `ValidateEntity` method is called: Here is where the context is added to the `items` dictionary, but it is useless now since it was needed in the entity `Validate` method. I think this procedure is no longer an option as a solution to make validations that needs to make a query through the context. – Felipe Correa Aug 19 '15 at 22:47
0

You need to get the context the entity is attached to to see its state. ValidationContext can be used to get extra information into the validation attribute class, it takes an implementation of IServiceProvdier when created which most IoC containers either implement or have wrappers for.

So if you are running in an environment with an IoC container and can control the creation of you attribute classes (e.g. by registering a DataAnnotationsModelValidator in MVC) and have rules for creating your context (e.g. per HTTP Request) you may be able to access to the current context in the attribute and use it to see the state of the entity being validated.

That's a lot of ifs though and without understand more context, what framework this code is running in and what behavior you are trying to achieve it is hard to say if this is a viable approach.

Mant101
  • 2,705
  • 1
  • 23
  • 27
0

@Gert Arnold post is a way,and if you want use attribute,you can find the object from ValidationContext by ObjectInstance property

var user = validationContext.ObjectInstance as User;
if(user==null) return new ValidationResult("...");
using(var db=new SomeDbContext()){
    var has=db.User.Any(x=>x.Id!=user.Id && x.email==value);
    if (has) return new ValidationResult("...");
    else return ValidationResult.Success;
}

when add,the id is 0,so in db,there is not a id 0 record,so if there is 1 record has same email,the any() return true.
when update,you have a id,so x.id!=user.id can except this record self.

chenZ
  • 920
  • 4
  • 16