web-dev-qa-db-fra.com

Modèle de référentiel avec cadre d'entité 4.1 et relations parent / enfant

J'ai encore une certaine confusion avec le motif de référentiel. La principale raison pour laquelle je souhaite utiliser ce modèle consiste à éviter d'appeler EF 4.1 Opérations d'accès aux données spécifiques du domaine. Je préférerais appeler des opérations de crud génériques à partir d'une interface IRÉPOSITOIRE. Cela facilitera les tests et si je devais changer le cadre d'accès aux données à l'avenir, je pourrai le faire sans refactoriser beaucoup de code.

Voici un exemple de ma situation:

J'ai 3 tables dans la base de données: Group, Person et GroupPersonMap. GroupPersonMap est une table de liaison et consiste simplement en Group et Person clés primaires. J'ai créé un modèle EF des 3 tables avec VS 2010 Designer. EF était assez intelligent pour assumer GroupPersonMap est une table de liaison afin qu'elle ne le montre pas dans le concepteur. Je souhaite utiliser mes objets de domaine existants au lieu des classes générées de EF afin que je désactive la génération de code pour le modèle.

Mes cours existants qui correspondent au modèle EF sont les suivants:

public class Group
{
   public int GroupId { get; set; }
   public string Name { get; set; }

   public virtual ICollection<Person> People { get; set; }
}

public class Person
{
   public int PersonId {get; set; }
   public string FirstName { get; set; }

   public virtual ICollection<Group> Groups { get; set; }
}

J'ai une interface de référentiel générique comme si:

public interface IRepository<T> where T: class
{
    IQueryable<T> GetAll();
    T Add(T entity);
    T Update(T entity);
    void Delete(T entity);
    void Save()
}

et un référentiel EF générique:

public class EF4Repository<T> : IRepository<T> where T: class
{
    public DbContext Context { get; private set; }
    private DbSet<T> _dbSet;

    public EF4Repository(string connectionString)
    {
        Context = new DbContext(connectionString);
        _dbSet = Context.Set<T>();
    }

    public EF4Repository(DbContext context)
    {
        Context = context;
        _dbSet = Context.Set<T>();
    }

    public IQueryable<T> GetAll()
    {
        // code
    }

    public T Insert(T entity)
    {
        // code
    }

    public T Update(T entity)
    {
        Context.Entry(entity).State = System.Data.EntityState.Modified;
        Context.SaveChanges();
    }

    public void Delete(T entity)
    {
        // code
    }

    public void Save()
    {
        // code
    }
}

Supposons maintenant que je veux juste mapper un _ existant Group à un Person. Je devrais faire quelque chose comme ce qui suit:

        EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
        EFRepository<Person> personRepository = new EFRepository<Person>("name=connString");

        var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
        var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();

        group.People.Add(person);
        groupRepository.Update(group);

Mais cela ne fonctionne pas car EF pense que Person est nouveau et essaiera de re -INSERT le Person dans la base de données qui entraînera une erreur de contrainte principale. Je dois utiliser DbSet 's _' s Attach méthode pour indiquer à EF que le Person existe déjà dans la base de données, il suffit de créer une carte entre Group et Person Dans la table GroupPersonMap.

Donc, afin de joindre Person au contexte, je dois maintenant ajouter une méthode Attach à mon IRÉPOSITOIRE:

public interface IRepository<T> where T: class
{
    // existing methods
    T Attach(T entity);
}

Pour corriger l'erreur principale de contrainte de clé:

EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context);

var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();

personRepository.Attach(person);
group.People.Add(person);
groupRepository.Update(group);

Fixé. Maintenant, je dois faire face à un autre problème sur lequel Group est en cours de mise à jour dans la base de données à chaque fois que je crée une carte de groupe/personne. C'est parce que dans ma méthode _ EFRepository.Update(), l'état d'entité est explicitement défini sur Modified'. I must set the Group's state to Inchangé so the La table de groupe n'est pas modifiée.

Pour résoudre ce problème, je dois ajouter une sorte de surcharge Update surcharge à mon IRÉPOSITOIRE qui ne met pas à jour l'entité racine ou Group, dans ce cas:

public interface IRepository<T> where T: class
{
    // existing methods
    T Update(T entity, bool updateRootEntity);
}

L'implémentation EF4 de la méthode de mise à jour ressemblerait à ceci comme suit:

T Update(T entity, bool updateRootEntity)
{
   if (updateRootEntity)
      Context.Entry(entity).State = System.Data.EntityState.Modified;
   else
      Context.Entry(entity).State = System.Data.EntityState.Unchanged;

    Context.SaveChanges();
}

Ma question est la suivante: Est-ce que je m'approche de cela la bonne façon? Mon référentiel commence à regarder EF Centric alors que je commence à travailler avec EF et le motif de référentiel. Merci de lire ce long post

35
jodev

La principale raison pour laquelle je souhaite utiliser ce modèle consiste à éviter d'appeler EF 4.1 Opérations d'accès aux données spécifiques du domaine. Je préférerais appeler des opérations de crud génériques à partir d'une interface IRÉPOSITOIRE. Cela facilitera les tests

Non It Ne facilitera pas vos tests . Vous avez exposé IQueryable Donc, votre référentiel n'est pas unitaire testable .

si je devais changer le cadre d'accès aux données à l'avenir, je pourrai le faire sans refactoriser beaucoup de code.

Non, vous devrez modifier beaucoup de code de toute façon parce que vous avez exposé IQueryable et parce que EF/ORM est une abstraction fuite - votre couche supérieure s'attend à ce que certains comportements se produisent comme par magie dans votre orj (par exemple, le chargement paresseux). C'est également l'une des raisons les plus étranges d'aller pour le référentiel. Choisissez simplement la bonne technologie maintenant et utilisez-la pour obtenir les paris de celui-ci. Si vous devez le modifier plus tard, cela signifie que ce soit celui Vous avez fait une erreur et avez choisi la mauvaise tâche que vous avez changé - Dans les deux cas, ce sera beaucoup de travail.

Mais cela ne fonctionne pas car EF pense que la personne est nouvelle et essaiera de réinsertion de la personne dans la base de données qui provoquera une erreur de contrainte principale.

Oui parce que vous utilisez un nouveau contexte pour chaque référentiel = c'est une mauvaise approche. Les référentiels doivent partager le contexte. Votre deuxième solution n'est pas correcte également car vous revenez votre dépendance EF à l'application - Le référentiel expose le contexte. Ceci est généralement résolu par le deuxième modèle - unité de travail. L'unité de travail enveloppe le contexte et unité de travail constitue l'ensemble de modifications atomiques - SaveChanges doit être exposé sur une unité de travail pour commettre des modifications effectuées par tous les référentiels associés.

Maintenant, j'ai un problème avec le groupe étant mis à jour dans la base de données à chaque fois que je souhaite créer une carte de groupe/personne.

Pourquoi changez-vous l'état? Vous avez reçu une entité du référentiel afin que vous l'avez détaché, il n'y a aucune raison d'appeler Attach et de modifier l'état manuellement. Tout cela devrait arriver automatiquement sur une entité attachée. Appelez simplement SaveChanges. Si vous utilisez des entités détachées, alors Vous devez définir correctement l'état pour chaque entité et relation, donc dans ce cas, vous devez en effet besoin de certaines surcharges logiques ou de mise à jour pour gérer tous les scénarios.

Est-ce que je m'approche de cela la bonne façon? Mon référentiel commence à regarder EF Centric alors que je commence à travailler avec EF et le motif de référentiel.

Je ne le pense pas. Tout d'abord, vous n'utilisez pas des racines agrégées. Si vous le faites, vous trouveriez immédiatement que référentiel générique ne convient pas à cela. Le référentiel de racines agrégées a des méthodes spécifiques par racine d'agrégat à gérer de travailler avec des relations agrégées par la racine. Group ne fait pas partie de Person agrégat, mais GroupPersonMap doit être si votre référentiel que votre personne doit avoir des méthodes spécifiques pour gérer l'ajout et la suppression de groupes de la personne (mais pas pour créer ou supprimer des groupes eux-mêmes). Le référentiel générique IMO est couche redondante .

67
Ladislav Mrnka