1

What are some best practices for handling validation of domain entities that depends on other domain entities using POCOs that one creates when implementing an ORM based off of the EF Code First?

Here's the situation I am trying to work through: I have a class that represents a client computer, and there is a property of that class that represents the computer's IP. I need that to be unique, but I cannot find an elegant solution for enforcing that constraint. Currently I do it in the service layer where I update/insert the entity.

-- UPDATE --

I know that EF doesn't support unique constraints and I have already added the constraint to the database table, but I'd rather catch the constraint before I hit the database. What I was looking for was a better way to handle validation that is dependent on other entities in general and was using the unique constraint as an example.

-- UPDATE 3/28/2010 --

For reference, here is how I currently handle the unique constraint for IP (_unitOfWork is of type SqlMessageUnitOfWork: basically it wraps around the DBContext I am using, exposing IDbSets for all relevant tables):

public class ClientService : IClientService
{
    public ValidationResult InsertClient(ClientDTO clientDTO)
    {
        var existingClient = _unitOfWork.Clients.Where(x => x.IP == clientDTO.IP).SingleOrDefault();

        if (existingClient != null)
        {           
            return new ValidationResult("IP already in Use.", new[] { "IP" });
        }
        else
        {
            var newclient = new Client();
            ClientEntityMapper.MapToEntity(clientDTO, newclient, _unitOfWork.Terminals);
            _unitOfWork.Clients.Add(newclient);
            _unitOfWork.Commit();
        }

        return ValidationResult.Success;
    }
    ...

    private IUnitOfWork _unitOfWork;

    public ClientService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
}

public interface IUnitOfWork
{
    IDbSet<Message> Messages { get; }
    IDbSet<Terminal> Terminals { get; }
    IDbSet<Client> Clients { get; }
    IDbSet<MessageDisplayInstance> MessageDisplayInstances { get; }
    void Commit();
}

public class SqlMessageUnitOfWork : IUnitOfWork
{
    readonly VisualPagingDbContext _context;

    public SqlMessageUnitOfWork()
    {
        _context = new VisualPagingDbContext();
    }

    public void Commit()
    {
        _context.SaveChanges();
    }

    public IDbSet<Message> Messages
    {
        get { return _context.Messages; }
    }

    public IDbSet<Terminal> Terminals
    {
        get { return _context.Terminals; }
    }

    public IDbSet<Client> Clients
    {
        get { return _context.Clients; }
    }

    public IDbSet<MessageDisplayInstance> MessageDisplayInstances
    {
        get { return _context.MessageDisplayInstances; }
    }
}
Merritt
  • 2,333
  • 21
  • 23
  • You didn't show your current way of handling the validation so we don't know what better way do you expect. – Ladislav Mrnka Mar 24 '11 at 21:52
  • I said I was handling it in the service layer. I will update my question by putting in the actual code. – Merritt Mar 28 '11 at 20:54
  • I don't think you can validate your dependent properties without querying the database. I define an IsValid() method in my BaseRepository class as a virtual method and override it in other repositories, making all the validation in a single method in service layer. – Mert Akcakaya Apr 20 '12 at 15:01
  • I agree, and although I forget the details of this question, I don't really use service classes like this anymore. To me they hide all the dirty little secrets that no one talks about when they spout off there repository/domain/service onion-based architecture. You can't get around the fact that validation (or business logic in general) sometimes depend on the state of other entities, and this tends to get pushed up into a 'service layer'. To me, it straight up belongs in the business model, so I see it as a violation of layer responsibility. – Merritt Apr 20 '12 at 21:27

3 Answers3

0

To add uniqueness checking to my entity validation, I overrode ValidateEntity in the context and added the following:

if (entityEntry.Entity is Role &&
            (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
        {
            var role = entityEntry.Entity as Role;

            if (!string.IsNullOrEmpty(role.ShortName))
            {
                if (
                    Roles.Any(
                        p => p.ShortName.ToLower() == role.ShortName.ToLower() && !p.RoleID.Equals(role.RoleID)))
                {
                    result.ValidationErrors.Add(new DbValidationError("ShortName", "Role Short Name already exists"));
                    return result.ValidationErrors.Count > 0 ? result : base.ValidateEntity(entityEntry, items);
                }
                //Remeber to check local collection - otherwise we could end up saving two or more at once that are duplicated
                if (
                    Roles.Local.Count(p => p.ShortName.ToLower() == role.ShortName.ToLower() && !p.Equals(role)) > 0)
                {
                    result.ValidationErrors.Add(new DbValidationError("ShortName", "Role Short Name already exists"));
                    return result.ValidationErrors.Count > 0 ? result : base.ValidateEntity(entityEntry, items);
                }
            }
        }

If you need to get EF to create constraints / indexes etc in the database, then please see:

Entity Framework Code First Fluent Api: Adding Indexes to columns

Community
  • 1
  • 1
jwsadler
  • 471
  • 4
  • 7
0

Unique constraints are not supported by EF at all (they are planned for future versions) but it doesn't mean you can't add them to your database tables (for example in custom initializer). What you describe is validation which requires query to database. It is a business validation which must be implemented and executed when needed. It is not something that EF will handle for you. Btw. CTP5 is outdated version. Use EF 4.1 instead.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
0

If you need to setup unique constraints you can do it through running a sql command.

So wherever you do your domain model configurations, you can add a line to execute a sql. So in your case the following should do the job:

string script = "ALTER TABLE <table-name> ADD CONSTRAINT UniqueElement UNIQUE (<column-name>)";
context.Database.SqlCommand(script);
Barry Kaye
  • 7,682
  • 6
  • 42
  • 64
Shukuboy
  • 9
  • 2
  • My question was not how to create unique constraints but how to better model them in my application code. – Merritt May 18 '11 at 15:01