2011-06-27 29 views
6

Estoy tratando de que EF 4.1 trabaje con Repository, UnitOfWork, separación de entidades de EF y validación.Entity Framework IValidatableObject reference DbContext

Seguí this guía para obtener una buena separación de mis entidades POCO del modelo EF y ahora estoy siguiendo la guía this para implementar la validación (con IValidatableObject).

Mi solución consiste en:

  • Contacts.Repository [referencias EF y Contacts.Entities]:
    • Contacts.edmx
    • ContactsDbContext.cs
  • Contacts.Entities [sin referencias]:
    • Contact.cs (clase Contacts.Entities.Contact parcial)
  • Contacts.Validation [referencias Contacts.Entities y Contacts.Repository]
    • Contact.cs (Contacts.Entities.Contact clase parcial)

Pero estoy golpeando una pared de ladrillo con la validación:

  1. No puedo agregar la lógica de validación a Contacts.Entities porque causaría una referencia circular con Contacts.Repository (contact.Validate (...) necesita usar ContactsDbContext). Así que creé un proyecto separado de Contacts.Validation.
  2. Pero, esto significa dividir la clase de contacto con clases parciales para definir el contacto dentro de ambos Contacts.Entities y Contacts.Validation. El código ya no se compila porque no se puede definir una clase parcial en diferentes conjuntos.

¿Alguien me ha dado algún consejo aquí? He colgado debajo del código ...

Contacts.Repository.ContactsDbContext.cs:

namespace Contacts.Repository 
{ 
    public partial class ContactsDbContext : DbContext 
    { 
    public DbSet<Contact> Contacts { get; set; } 

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

Contacts.Entities.Contact.cs:

namespace Contacts.Entities 
{ 
    public partial class Contact 
    { 
     public string Name { get; set; } 
    } 
} 

Contacts.Validation.Contact. cs contiene:

namespace Contacts.Entities 
{ 
    public partial class Contact : IValidatableObject 
    { 
     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
     { 
      ContactsDbContext contacts = (ContactsDbContext)validationContext.Items["Context"]; 

      //Check if Contact already exists with the same Name 
      if (contacts.Any<Contact>(c => c.Name == this.Name)) 
      yield return new ValidationResult("Contact 'Name' is already in use.", new string[] { "Name" }); 

      yield break; 
     } 
    } 

Respuesta

7

Técnicamente se podría introducir una interfaz con una implementación explícita de este modo:

En Contacts.Entities montaje:

public interface IContactsDbContext 
{ 
    IQueryable<Contact> Contacts { get; } 
    // Not DbSet<Contact> because you don't want dependency on EF assembly 
} 

//... 

public class Contact : IValidatableObject // No partial class anymore 
{ 
    public string Name { get; set; } 

    public IEnumerable<ValidationResult> Validate(
     ValidationContext validationContext) 
    { 
     IContactsDbContext context = 
      validationContext.Items["Context"] as IContactsDbContext; 

     if (context.Contacts.Any<Contact>(c => c.Name == this.Name)) 
      yield return new ValidationResult(
       "Contact 'Name' is already in use.", new string[] { "Name" }); 

     yield break; 
    } 
    // IValidatableObject, ValidationResult and ValidationContext is in 
    // System.ComponentModel.DataAnnotations.dll, so no dependency on EF 
} 

En Contacts.Repository montaje (referencias Contactos.Entidades montaje):

public class ContactsDbContext : DbContext, IContactsDbContext 
{ 
    public DbSet<Contact> Contacts { get; set; } 

    IQueryable<Contact> IContactsDbContext.Contacts // explicit impl. 
    { 
     get { return Contacts; } // works because DbSet is an IQueryable 
    } 

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

Contacts.Validation montaje se puede quitar.

Sin embargo, realmente no me gusta esta solución. Su POCO tiene, a través del método Validate, una dependencia del repositorio, si es interfaz o no. Para una separación más fuerte de las preocupaciones, probablemente preferiría tener una clase de Validación por separado, que quizás también haga operaciones en el repositorio. O si implementara IValidatableObject, probablemente solo haga validaciones que dependan solo de las propiedades del objeto modelo (cosas como "la fecha de producción no debe ser posterior a la fecha de envío", etc.). Bueno, es parcialmente una cuestión de gusto. El segundo ejemplo que has vinculado realmente no se preocupa por la separación de las preocupaciones, por lo que tienes un conflicto de alguna manera con el primer ejemplo.

+0

Gracias. - Eso es un paso más cerca. Estoy de acuerdo, preferiría más separación de preocupaciones. ¿Cómo separarías normalmente la Validación del Repositorio (independientemente de EF)? Tengo un buen patrón de Validación y Repositorio que proporciona una separación completa ... pero luego tiene el problema de que la persona que llama no puede llamar al Validate antes de llamar al Guardar en el Repositorio. NB: ahora tengo que hacer referencia a Entity Framework en los llamadores porque la validación generará un tipo System.Data.Entity.Validation.DbEntityValidationException. Estoy descubriendo que esta es una pelea molesta contra EF !!! – Matt

+1

@Matt: No tengo una separación clara entre la validación y el repositorio. La validación de que un campo particular debe ser único es desde mi punto de vista, no una validación a nivel de entidad. También podría considerarse como una validación del repositorio (el repositorio se volvería inválido * si * insertaría una entidad con el mismo nombre). Normalmente accedo a mis repos a través de clases de servicio y allí hago un "chequeo" antes de insertar si ya hay una entidad con el mismo nombre. No es una validación limpiamente separada. Puede ser más fácil y más limpio cuando EF proporciona la característica "Restricciones únicas" mencionada en la segunda publicación del blog. – Slauma

+0

Estoy usando DataAnnotation para proporcionar validación de nivel de entidad. Estoy de acuerdo en que la verificación de unicidad es un nivel de validación diferente y no debería estar realmente en la entidad. TBH, siento que EF me está forzando a seguir este camino. Veré cómo moverlo al repositorio. Gracias por tu ayuda. – Matt

1

La validación de que un campo particular debe ser único es desde mi punto de vista no una validación en nivel de entidad. También se podría considerar una validación del repo (el repositorio dejaría de ser válido si insertara una entidad con el mismo nombre).

Normalmente accedo a mis repos a través de clases de servicio y allí hago una "comprobación" antes de insertar si ya hay una entidad con el mismo nombre. No es una validación limpiamente separada. Puede ser más fácil y más limpio cuando EF proporciona la característica "Restricciones únicas" mencionada en la segunda publicación del blog.

~ Slauma Jun 28 '11 at 17:14

este comentario merecía ser una respuesta

Cuestiones relacionadas