35

Ho ancora un po 'di confusione con il pattern di repository. Il motivo principale per cui voglio utilizzare questo modello è di evitare di chiamare EF 4.1 specifiche operazioni di accesso ai dati dal dominio. Preferisco chiamare le operazioni generiche CRUD da un'interfaccia IRepository. Ciò renderà i test più facili e se dovessi cambiare il framework di accesso ai dati in futuro, potrò farlo senza dover refactoring un sacco di codice.Schema di repository con Entity Framework 4.1 e relazioni padre/figlio

Ecco un esempio della mia situazione:

Ho 3 tabelle nel database: Group, Person e GroupPersonMap. GroupPersonMap è una tabella di collegamento e consiste semplicemente nelle chiavi primarie Group e Person. Ho creato un modello EF dei 3 tavoli con il designer VS 2010. EF è stato abbastanza intelligente da supporre che GroupPersonMap sia una tabella di collegamenti in modo che non venga visualizzata nella finestra di progettazione. Voglio utilizzare i miei oggetti dominio esistenti invece delle classi generate da EF, quindi spengo la generazione del codice per il modello.

mie classi esistenti che corrisponde al modello di EF sono i seguenti:

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; } 
} 

Ho un'interfaccia generica repository in questo modo:

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

e un repository EF generico:

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 
    } 
} 

Ora supponiamo di voler mappare uno Group esistente a unoesistente. Avrei dovuto fare qualcosa di simile al seguente:

 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); 

Ma questo non funziona perché EF pensa Person è nuovo, e cercherò di ri- INSERT il Person nel database, che causerà un errore di vincolo di chiave primaria . Devo utilizzare il metodo Attach di Attach per indicare a EF che il Person esiste già nel database, quindi è sufficiente creare una mappa tra Group e Person nella tabella GroupPersonMap.

Quindi, al fine di collegare Person al contesto ora devo aggiungere un metodo Attach al mio IRepository:

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

Per fissare il primario errore di vincolo di chiave:

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); 

fisso. Ora devo occuparmi di un altro problema in cui Group viene aggiornato nel database ogni volta che creo una mappa di gruppo/persona. Questo perché nel mio metodo EFRepository.Update(), lo stato dell'entità è impostato esplicitamente su Modified'. I must set the Group's state to Invariato so the La tabella di gruppo non viene modificata.

Per correggere questo devo aggiungere una sorta di Update sovraccarico al mio IRepository che non aggiorna l'entità root, o Group, in questo caso:

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

L'implentation EF4 del metodo di aggiornamento potrebbe essere qualcosa così:

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(); 
} 

La mia domanda è: mi sto avvicinando a questo nel modo giusto? Il mio repository sta iniziando a sembrare EF centric mentre comincio a lavorare con EF e il pattern del repository. Grazie per aver letto questo lungo post

risposta

66

The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier

No, will not make your testing easier. You exposed IQueryable quindi il repository is not unit testable.

if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.

Non si dovrà cambiare un sacco di codice in ogni caso perché è esposto IQueryable e perché EF/ORM è l'astrazione che perde - il vostro strato superiore si aspetta un comportamento accade magicamente all'interno del vostro ORM (ad esempio caricamento pigro). Anche questo è uno dei motivi più strani per andare per repository. Basta scegliere la tecnologia giusta ora e usarla per ottenere le scommesse. Se devi cambiarlo in un secondo momento, significa che è you did a mistake and chose the wrong one or requirements have changed - in entrambi i casi sarà molto lavoro.

But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error.

Sì perché si sta utilizzando un nuovo contesto per ogni repository = che è un approccio sbagliato. I repository devono condividere il contesto. Anche la tua seconda soluzione non è corretta, perché rimetti la dipendenza EF all'applicazione: il repository espone il contesto. Solitamente questo è risolto dal secondo schema: unità di lavoro. Unit of work wraps the context e unità di lavoro forma il set di modifica atomica - SaveChanges deve essere esposto sull'unità di lavoro per eseguire il commit delle modifiche eseguite da tutti i repository correlati.

Now I have an issue with the Group being UPDATE'd in the database every time I want to create a Group/Person map.

Perché si modifica lo stato? Hai ricevuto un'entità dal repository così finché non lo hai staccato non c'è motivo di chiamare Attach e cambiare lo stato manualmente. Tutto ciò dovrebbe avvenire automaticamente sull'entità allegata. Basta chiamare il numero SaveChanges. Se si utilizzano entità distaccate, quindi you must correctly set state for every entity e relazione, in tal caso sarà necessario un sovraccarico logico o di aggiornamento per gestire tutti gli scenari.

Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern.

Non credo. Prima di tutto non stai usando radici aggregate. Se lo fai, vedresti immediatamente che generic repository non è adatto a questo. Il repository per le root aggregate ha metodi specifici per radice aggregata per gestire il lavoro con le relazioni aggregate dalla radice. Group non fa parte dell'aggregato Person ma GroupPersonMap deve essere tale che il repository personale dovrebbe disporre di metodi specifici per gestire l'aggiunta e la rimozione di gruppi da persona (ma non per creare o eliminare gruppi stessi). Il repository generico Imo è redundant layer.

+4

+1 Ottima risposta –

+1

+1 Molto informativo –

+0

+1 per entità quadro + unità di lavoro. I repository devono condividere lo stesso dbcontext. Scommetto che è quello che è il mio problema. – Jhayes2118

Problemi correlati