posts - 315, comments - 268, trackbacks - 15

My Links

News

View Pietro Libro's profile on LinkedIn

DomusDotNet
   DomusDotNet

Pietro Libro

Tag Cloud

Article Categories

Archives

Post Categories

Blogs amici

Links

EF CTP 5: Episode III (Data Validation)

 

La validazione è sicuramente una delle feature più interessanti introdotte con la CTP 5, anche se disponibile solo “in modalità” Code First, almeno nel momento in cui si scrive (su questo punto penso che se ne potrebbe parlare…). Bisogna fare una piccola precisazione: in questo post, per validazione, non intendiamo le Business Rule  del Domain che sono generalmente più complicate della validazione effettuata sui dati che devono essere persistiti: richiesto, non richiesto, lunghezza minima di un campo ecc … Prima di iniziare a vedere qualche esempio di codice, qualche piccola precisazione: la validazione è abilitata per default, per eseguirla sono utilizzati gli attributi presenti nel namespace System.ComponentModel.DataAnnotations e derivanti dalla classe ValidationAttribute.

Riprendiamo le classi degli esempi precedenti, decoriamo la classe Developer con qualche attributo in questo modo:

[Table("Sviluppatore")]
public class Developer
{
    public Developer()
    {
        this.Skills = new List<Skill>(3);
        this.Projects = new List<Project>(1);
    }

    public int Id { get; set; }

    [Required]
    [MaxLength(30, ErrorMessage = "Maximum lenght for name is 30 chars")]
    public string Name { get; set; }

    [Required]
    [MaxLength(30, ErrorMessage = "Maximum lenght for surname is 30 chars")]
    public string Surname { get; set; }

    [Required]
    public ICollection<Skill> Skills { get; set; }
    public ICollection<Project> Projects { get; set; }
}

Le proprietà Name e Surname sono decorate con  gli attributi Required ad indicare che i campi sono obbligatori e con MaxLenght che specifica quale sia la lunghezza massima in numero di caratteri, per entrambe le proprietà fissata a 30 caratteri. Da notare che abbiamo definito anche un messaggio di errore per capire, come vedremo tra poco, eventuali errori di validazione. Stesso discorso per la Navigation Properties Skills, dove vogliamo che per ogni sviluppatore sia aggiunta almeno una competenza. Modifichiamo il codice del Main in questo modo:

using (Db db = new Db())
{
    try
    {
        int developers = 0;
        int projects = 0;

        ////Add a new Developer
        Developer dev1 = new Developer();

        db.Developers.Add(dev1);

        //////Save data.
        int rowsAffected = db.SaveChanges();

        developers = (from d in db.Developers select d).Count();
        projects = (from p in db.Projects select p).Count();

        Console.WriteLine("Projects in db: {0}", developers);
        Console.WriteLine("Developers in db: {0}", developers);
    }
    catch (DbEntityValidationException ex)
    {
        foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
        {
            if (!validationResult.IsValid)
            {
                Console.WriteLine("Error in " + validationResult.Entry.Entity.ToString());

                foreach (DbValidationError validationError in validationResult.ValidationErrors)
                {
                    Console.WriteLine("{0} : {1}", validationError.PropertyName, validationError.ErrorMessage);
                }
            }
        }
    }
}

Nella parte finale del codice abbiamo aggiunto un try…catch per catturare un’eventuale eccezione di tipo DbEntityValidationException, la quale contiene una collezione di DbEntityValidationResult (una per ogni entità) che sua volta espone la collezione ValidationErrors che possiamo visitare per visualizzare su console (in questo caso) gli eventuali errori di validazione dei dati, come in figura:

image

Se modifichiamo il codice per la creazione di uno sviluppatore in questo modo:

////Add a new Developer
Developer dev1 = new Developer()
    {
        Name = "Pinco",
        Surname = "Pallo"
    };

ed eseguiamo, questa volta tutto ok:

image

Attenzione, non è vero che è tutto ok: inizialmente abbiamo specificato la collezione Skills come Required, ma dato che nel costruttore di Developer abbiamo scritto:

    public Developer()
    {
        this.Skills = new List<Skill>(3);
        this.Projects = new List<Project>(1);
    }

per EF è tutto ok. Noi  invece vogliamo che la collezione contenga almeno un elemento, per far questo creiamo un nuovo attributo che definiamo NavigationAttribute:

    public class NavigationAttribute : ValidationAttribute
    {
        public int MinLenght { get; set; }

        public override bool IsValid(object value)
        {
            if (value == null) 
                return false;
            int count = (int)value.GetType().GetMethod("get_Count").Invoke(value, null);
            return (count >= MinLenght);
        }
    }

Eseguiamo l’override del metodo IsValid per definire il comportamento della regola di validazione: 1) la collezione non deve essere Null 2) tramite reflection invochiamo il metodo Count della collezione (get_Length nel caso di Array) per verificare quanti elementi questa contenga. Nel caso il valore restituito da Count sia minore di MinLenght indichiamo al motore di validazione di EF che per quest’entità la validazione non è andata a buon fine. Vediamo l’uso dell’attributo Custom:

    [Required]
    [Navigation(MinLenght = 1, ErrorMessage = "A developer requires al least one skill.")]
    public ICollection<Skill> Skills { get; set; }

Anche in questo aggiungiamo un messaggio personalizzato di errore. A questo punto rieseguendo l’applicazione dovremmo ottenere qualcosa di simile:

 image

Per rimuovere ogni forma di validazione è sufficiente impostare a false il corrispondente valore di configurazione:

db.Configuration.ValidateOnSaveEnabled = false;

La validazione delle entità può essere effettuata in qualsiasi momento richiamando GetValidationErrors(), quindi non solo in fase di salvataggio:
IEnumerable<DbEntityValidationResult> validationResults = db.GetValidationErrors();

foreach (DbEntityValidationResult validationResult in validationResults)
{
    Console.WriteLine("Error in " + validationResult.Entry.Entity.ToString());

    foreach (DbValidationError validationError in validationResult.ValidationErrors)
    {
        Console.WriteLine("{0} : {1}", validationError.PropertyName, validationError.ErrorMessage);
    }
}

Oppure è possibile eseguire la validazione di una singola entità in questo modo:

DbEntityValidationResult validationResult = db.Entry<Developer>(dev1).GetValidationResult();
    if (!validationResult.IsValid)
    {
        foreach (DbValidationError validationError in validationResult.ValidationErrors)
        {
            Console.WriteLine("{0} : {1}", validationError.PropertyName, validationError.ErrorMessage);
        }

        Console.ReadLine();
        return;
    }

Volendo è possibile effettuare la validazione anche di singole proprietà. Ovviamente in tutti i casi il risultato che si ottiene è sempre lo stesso. Un altro attributo di validazione molto interessante è quello che permette di eseguire validazioni tramite Regular Expression ad esempio nel caso della proprietà e-mail dell’entità architetto potremmo decorare la classe in questo modo:

[Table("Architetto")]
public class Architect : Developer
{
    [RegularExpression(@"\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b")]
    public string Email { get; set; }
    public string Phone { get; set; }
}

Un altro tassello nel puzzle della  CTP 5 di EF Code First.

Print | posted on venerdì 21 gennaio 2011 17:43 | Filed Under [ .Net Framework 4.0 Entity Framework 4 ]

Feedback

Gravatar

# re: EF CTP 5: Episode III (Data Validation)

Ciao, grazie a voi che leggete e che li commentate, altrimenti avrebbe poco senso scrivere e parlarne.
Per quanto concerne la validazione, hai ragione, ma nel caso di Complex Type, la validazione precedente richiedeva di scrivere del codice, in questo caso è "automatica".
Come Andrea ha giustamente evidenziato nei post precedenti, quello che noi costruiamo non è il Domain Model, ma una sorta di Data Model, in quest'ottica la validazione delle regole di Business sicuramente non le farei a questo livello.
Per quanto concerne gli attributi e gli oggetti POCO, penso IMHO che effettivamente si perde il concetto di Persistence Ignorance come siamo abituati nell'approccio Model First.
25/01/2011 17:07 | Pietro L.
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET