2014-09-26 18 views
28

Creazione di un nuovo progetto MVC e come l'idea di repository nel livello dati, quindi li ho implementati. Ho anche creato un livello di servizio per gestire tutta la logica e la convalida aziendale, questo strato a sua volta utilizza il repository appropriato. Qualcosa di simile a questo (sto usando iniettore semplice per iniettare)Entity Framework 6 e Unit of Work ... Dove, Quando? È come le transazioni in ado.net?

DAL STRATO

public class MyRepository { 

    private DbContext _context; 
    public MyRepository(DbContext context) { 
     _context = context; 
    }  

    public MyEntity Get(int id) 
    { 
     return _context.Set<MyEntity>().Find(id); 
    } 

    public TEntity Add(MyEntity t) 
    { 
     _context.Set<MyEntity>().Add(t); 
     _context.SaveChanges(); 
     return t; 
    } 

    public TEntity Update(MyEntity updated, int key) 
    { 
     if (updated == null) 
      return null; 

     MyEntity existing = _context.Set<MyEntity>().Find(key); 
     if (existing != null) 
     { 
      _context.Entry(existing).CurrentValues.SetValues(updated); 
      _context.SaveChanges(); 
     } 
     return existing; 
    } 

    public void Delete(MyEntity t) 
    { 
     _context.Set<MyEntity>().Remove(t); 
     _context.SaveChanges(); 
    } 
} 

livello di servizio

public class MyService { 
    private MyRepository _repository; 

    public MyService(MyRepository repository) { 
     _repository = repository;  
    } 

    public MyEntity Get(int id) 
    { 
     return _repository.Get(id); 
    } 

    public MyEntity Add(MyEntity t) 
    { 
     _repository.Add(t); 

     return t; 
    } 

    public MyEntity Update(MyEntity updated) 
    { 
     return _repository.Update(updated, updated.Id); 
    } 

    public void Delete(MyEntity t) 
    { 
     _repository.Delete(t); 
    } 
} 

Ora questo è molto semplice, in modo che io possa utilizzare il seguente codice per aggiornare un oggetto.

MyEntity entity = MyService.Get(123); 
MyEntity.Name = "HELLO WORLD"; 
entity = MyService.Update(entity); 

O questo per creare un oggetto

MyEntity entity = new MyEntity(); 
MyEntity.Name = "HELLO WORLD"; 
entity = MyService.Add(entity); 
// entity.Id is now populated 

Ora dire che ho bisogno di aggiornare un elemento basato sulla creazione Id di un altro, ho potuto utilizzare il codice di cui sopra tutti bene, ma che cosa succede se un si verifica un errore? Ho bisogno di una sorta di transazione/rollback. È questo che supponiamo che il modello dell'Unità di lavoro risolva?

Quindi suppongo di aver bisogno di avere DbContext nel mio oggetto UnitOfWork, quindi creo un oggetto come questo?

public class UnitOfWork : IDisposable { 

    private DbContext _context; 

    public UnitOfWork(DbContext context) { 
     _context = context; 
    } 

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

    public Dispose() { 
     _context.Dispose(); 
    } 

} 

Ok così ancora, questo è abbastanza semplice. UnitOfWork mantiene anche il contesto (io uso lo stesso contesto in tutti i repository) e chiama il metodo SaveChanges(). Quindi rimuoverei la chiamata al metodo SaveChanges() dal mio repository. Quindi aggiungere vorrei fare quanto segue:

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow 

MyEntity entity = new MyEntity(); 
MyEntity.Name = "HELLO WORLD"; 
entity = MyService.Add(entity); 

uow.Commit(); 

Ma cosa succede se ho bisogno di creare un oggetto e quindi aggiornare altri oggetti basati su tale Id, questo ora non funziona, perché l'ID non verrà creato fino a quando ho chiama Commit sul uow. Esempio

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow 

MyEntity entity = new MyEntity(); 
MyEntity.Name = "HELLO WORLD"; 
entity = MyService.Add(entity); 
// entity.Id is NOT populated 

MyEntity otherEntity = MyService.Get(123); 
otherEntity.OtherProperty = entity.Id; 
MyService.Update(otherEntity); 

uow.Commit(); // otherEntity.OtherProperty is not linked.....? 

Così ho la sensazione che questa classe UnitOfWork non è giusto ... forse io sono la signorina comprensione qualcosa.

Devo essere in grado di aggiungere un'entità e ottenere quell'ID e usarlo su un'altra entità, ma se si verifica un errore, voglio eseguire il "rollback" come farebbe una transazione ado.net.

Questa funzionalità è possibile utilizzando Entity Framework e repository?

risposta

32

Devo dire innanzitutto che non esiste un modo giusto per risolvere questo problema. Sto solo presentando qui quello che probabilmente farei.


La prima cosa è, DbContext si implementa l'Unità di modello di lavoro. Chiamare il numero SaveChangesfa creare una transazione DB in modo che ogni query eseguita sul DB venga ripristinata in modo errato.

Ora, c'è un grosso problema nel design attuale che hai: il tuo repository chiama SaveChanges su DbContext.Ciò significa che devi rendere XXXRepository responsabile di commettere tutte le modifiche apportate sull'unità di lavoro, non solo le modifiche sulle entità XXX di cui è responsabile il repository.

Un'altra cosa è che DbContext è anch'esso un repository. Quindi l'astrazione dell'uso di DbContext all'interno di un altro repository crea solo un'altra astrazione su un'astrazione esistente, che è semplicemente troppo IMO di codice.

Oltre al fatto che potrebbe essere necessario accedere a XXX entità dal repository YYY e alle entità YYY dal repository XXX, così per evitare dipendenze circolari si finirà con un MyRepository : IRepository<TEntity> inutile che duplica solo tutti i metodi DbSet.

Vorrei rilasciare l'intero livello del repository. Vorrei utilizzare lo DbContext direttamente all'interno del livello di servizio. Naturalmente, puoi considerare tutte le query complesse che non vuoi duplicare nel livello di servizio. Qualcosa di simile:

public MyService() 
{ 
    ... 
    public MyEntity Create(some parameters) 
    { 
     var entity = new MyEntity(some parameters); 
     this.context.MyEntities.Add(entity); 

     // Actually commits the whole thing in a transaction 
     this.context.SaveChanges(); 

     return entity; 
    } 

    ... 

    // Example of a complex query you want to use multiple times in MyService 
    private IQueryable<MyEntity> GetXXXX_business_name_here(parameters) 
    { 
     return this.context.MyEntities 
      .Where(z => ...) 
      ..... 
      ; 
    } 
} 

Con questo modello, ogni chiamata pubblica su una classe di servizio viene eseguito all'interno di una transazione grazie alla DbContext.SaveChanges essere transazionale.

Ora, per l'esempio che si ha con l'ID richiesto dopo il primo inserimento di entità, una soluzione non deve utilizzare l'ID ma l'entità stessa. Quindi lascia che Entity Framework e la sua implementazione del modello di unità di lavoro si occupino di esso.

Così, invece di:

var entity = new MyEntity(); 
entity = mydbcontext.Add(entity); 
// what should I put here? 
var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123); 
otherEntity.OtherPropertyId = entity.Id; 

uow.Commit(); 

si ha:

var entity = new MyEntity(); 
entity = mydbcontext.Add(entity); 

var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123); 
otherEntity.OtherProperty = entity;  // Assuming you have a navigation property 

uow.Commit(); 

se non si dispone di una proprietà di navigazione, o se si dispone di un caso d'uso più complesso da affrontare, la soluzione è quello di utilizzare la buona transazione oro all'interno del vostro metodo di servizio pubblico:

public MyService() 
{ 
    ... 
    public MyEntity Create(some parameters) 
    { 
     // Encapuslates multiple SaveChanges calls in a single transaction 
     // You could use a ITransaction if you don't want to reference System.Transactions directly, but don't think it's really useful 
     using (var transaction = new TransactionScope()) 
     { 
      var firstEntity = new MyEntity { some parameters }; 
      this.context.MyEntities.Add(firstEntity); 

      // Pushes to DB, this'll create an ID 
      this.context.SaveChanges(); 

      // Other commands here 
      ... 

      var newEntity = new MyOtherEntity { xxxxx }; 
      newEntity.MyProperty = firstEntity.ID; 
      this.context.MyOtherEntities.Add(newEntity); 

      // Pushes to DB **again** 
      this.context.SaveChanges(); 

      // Commits the whole thing here 
      transaction.Commit(); 

      return firstEntity; 
     } 
    } 
} 

Si può anche chiamare ser multipla metodo vices all'interno di uno scope transazionale se richiesto:

public class MyController() 
{ 
    ... 

    public ActionResult Foo() 
    { 
     ... 
     using (var transaction = new TransactionScope()) 
     { 
      this.myUserService.CreateUser(...); 
      this.myCustomerService.CreateOrder(...); 

      transaction.Commit(); 
     } 
    } 
} 
+0

grazie per aver spiegato e fornito un esempio. Consigli presi – Gillardo

+0

@ ken2k per quanto riguarda 'Separazione delle preoccupazioni' e' Database Domain Enotostic Layer'? – tchelidze

+0

@tchelidze E riguardo a loro? –

Problemi correlati