Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Entity Framework: Data validation between add to context and saveChanges()

I have a simple scenario using the Entity Framework in C#. I have an Entity Post:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

In my PostManager I have these methods:

public int AddPost(string name, string description)
    {
        var post = new Post() { Name = name, Description = description };

        using (var db = new DbContext())
        {
          var res = db.Posts.Add(post);
          res.Validate();
          db.SaveChanges();
          return res.Id;
        }
    }

    public void UpdatePost(int postId, string newName, string newDescription)
    {
        using (var db = new DbContext())
        {
            var data = (from post in db.Posts.AsEnumerable()
                where post.Id == postId
                select post).FirstOrDefault();

            data.Name = newName;
            data.Description = newDescription;
            data.Validate();
            db.SaveChanges();
        }
    }

The method validate() refers to class:

public static class Validator
{
    public static void Validate(this Post post)
    {
        if ( // some control)
            throw new someException();
    }

I call the validate method before the savechanges() but after adding the object to the context. What's the best practice to validate data in this simple scenario? It's better validate the arguments instead? What's happen to object post if the validate method throw exception after adding the object to the context?

UPDATE:

I have to throw a custom set of exception depending on data validation error.

like image 270
Andrea Moraglia Avatar asked Jan 21 '16 13:01

Andrea Moraglia


Video Answer


2 Answers

I strongly recommend you to (if at all possible) to modify your entity so the setters are private (don't worry, EF can still set them on proxy creation), mark the default constructor as protected (EF can still do lazy loading/proxy creation), and make the only public constructors available check the arguments.

This has several benefits:

  • You limit the number of places where the state of an entity can be changed, leading to less duplication
  • You protect your class' invariants. By forcing creation of an entity to go via a constructor, you ensure that it is IMPOSSIBLE for an object of your entity to exist in an invalid or unknown state.
  • You get higher cohesion. By putting the constraints on data closer to the data itself, it becomes easier to understand and reason about your classes.
  • You code becomes self-documenting to a higher degree. One never has to wonder "is it OK if I set a negative value on this int property?" if it is impossible to even do it in the first place.
  • Separation of concerns. Your manager shouldn't know how to validate an entity, this just leads to high coupling. I've seen many managers grow into unmaintainable monsters because they simply do everything. Persisting, loading, validation, error handling, conversion, mapping etc. This is basically the polar opposite of SOLID OOP.

I know it is really popular nowadays to just make all "models" into stupid property bags with getters and setters and only a default constructor because (bad) ORMs have forced us to do this, but this is no longer the case, and there are so many issues with this imo.

Code example:

public class Post
{
    protected Post() // this constructor is only for EF proxy creation
    {
    }

    public Post(string name, string description)
    {
        if (/* validation check, inline or delegate */)
            throw new ArgumentException();

        Name = name;
        Description = description;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
}

Then your PostManager code becomes trivial:

using (var db = new DbContext())
{
    var post = new Post(name, description); // possibly try-catch here
    db.Posts.Add(post);
    db.SaveChanges();
    return post.Id;
}

If the creation/validation logic is extremely intricate this pattern lends itself very well for refactoring to a factory taking care of the creation.

I would also note that encapsulating data in entities exposing a minimal state-changing API leads to classes that are several orders of magnitude easier to test in isolation, if you care at all about that sort of thing.

like image 145
sara Avatar answered Sep 30 '22 18:09

sara


As I mentioned in the comments above, you might want to check out .NET System.ComponentModel.DataAnnotations namespace.

Data Annotations (DA) allows you to specify attributes on properties to describe what values are acceptable. It's important to know that DA is completely independent of databases and ORM APIs such as Entity Framework so classes decorated with DA attributes can be used in any tier of your system whether it be the data tier; WCF; ASP.NET MVC or WPF.

In the example below, I define a Muppet class with a series of properties.

  • Name is required and has a max length of 50.

  • Scaryness takes an int but it must be in the range of {0...100}.

  • Email is decorated with an imaginary custom validator for validating strings that should contain an e-mail.

Example:

public class Muppet
{
    [Required]
    [StringLength(50)]
    public string Name {get; set;}  

    public Color Color {get; set; }

    [Range(0,100)]
    public int Scaryness {get; set; }

    [MyCustomEmailValidator]
    public string Email {get;set; }
}

In my project I have to throw customException when i validate the data. It's possible do it using Data Annotations?

Yes you can. To validate this object at any time of your application (regardless of whether it has reached EF or not) just perform this:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

.
.
.
Post post = ... // fill it in
Validator.Validate(post);

public static class Validator
{
    public static void Validate(this Post post)
    {
        // uses the extension method GetValidationErrors defined below
        if (post.GetValidationErrors().Any())
        {
            throw new MyCustomException();
        }
     }
}


public static class ValidationHelpers
{

    public static IEnumerable<ValidationResult> GetValidationErrors(this object obj)
    {
        var validationResults = new List<ValidationResult>();
        var context = new ValidationContext(obj, null, null);
        Validator.TryValidateObject(obj, context, validationResults, true);
        return validationResults;
    }
.
.
.

If you want to get the validation error messages you could use this method:

    /// <summary>
    /// Gets the validation error messages for column.
    /// </summary>
    /// <param name="obj">The object.</param>
    /// <returns></returns>
    public static string GetValidationErrorMessages(this object obj)
    {
        var error = "";

        var errors = obj.GetValidationErrors();
        var validationResults = errors as ValidationResult[] ?? errors.ToArray();
        if (!validationResults.Any())
        {
            return error;
        }

        foreach (var ee in validationResults)
        {
            foreach (var n in ee.MemberNames)
            {
                error += ee + "; ";
            }
        }

        return error;
    }

The free set of steak knives is that the validation attributes will be detected once the object reaches EF where it will be validated there as well in case you forget or the object is changed since.

like image 33
MickyD Avatar answered Sep 30 '22 18:09

MickyD