Entity Framework 4 - Dove mettere la logica "ApplicacurrentValues"?
-
26-09-2019 - |
Domanda
Sto usando il "tecnica di stub" a aggiornare Il mio POCO (usato in un contesto indipendente, ASP.NET MVC).
Questo è il codice che ho attualmente nel mio controller (che funziona):
[HttpPost]
public ActionResult Edit(Review review)
{
Review originalReview = _userContentService.FindById(review.PostId) as Review;
var ctx = _unitOfWork as MySqlServerObjectContext;
ctx.ApplyCurrentValues("MyEntities.Posts", review);
_unitOfWork.Commit();
// ..snip - MVC stuff..
}
Come puoi vedere, c'è un odore di codice ovunque. :)
Alcuni punti:
- Uso l'iniezione di dipendenza (basata sull'interfaccia) per praticamente tutto
- Uso il modello di unità di lavoro per astrarre ObjectContext e fornire persistenza in più repository
- Attualmente il mio Iunitofwork L'interfaccia ha solo 1 metodo:
void Commit();
- Controller
IUserContentService
eIUnitOfWork
iniettare per di IUserContentService
chiamateFind
nei repository, che usano ilObjectContext
.
Queste sono due cose che non mi piacciono con il mio codice sopra:
- Non voglio lanciare il Iunitofwork come
MySqlServerObjectContext
. - Non voglio che il controller debba interessare
ApplyCurrentValues
Fondamentalmente voglio che il mio codice sembri così:
[HttpPost]
public ActionResult Edit(Review review)
{
_userContentService.Update(review);
_unitOfWork.Commit();
// ..snip - MVC stuff..
}
Qualche idea su come posso farlo? (o qualcosa di simile).
Ho già intelligenza per elaborare il nome del set di entità in base al tipo (combinazione di generici, pluralizzazione), quindi non preoccuparti troppo.
Ma Mi chiedo dove il posto migliore da mettere ApplyCurrentValues
è? Non sembra appropriato dirlo nel IUnitOfWork
interfaccia, poiché questa è una preoccupazione di persistenza (EF). Per lo stesso motivo non appartiene al servizio. Se lo metto nel mio MySqlServerObjectContext
Classe (ha senso), da dove chiamerei questo, poiché nulla ha direttamente l'accesso a questa classe: viene iniettato tramite DI quando qualcosa richiede IUnitOfWork
.
qualche idea?
MODIFICARE
Di seguito ho una soluzione che utilizza la tecnica stub, ma il problema è che se avessi recuperato l'entità che sto aggiornando in anticipo, lancia un'eccezione, affermando che esiste già un'entità con quella chiave.
Il che ha senso, anche se non sono sicuro di come può risolverlo?
Devo "verificare se l'entità è già allegata, in caso contrario, allegala?"
Qualche esperto EF4 può aiutare?
MODIFICARE
Nevermind - Ho trovato la soluzione, vedi risposta di seguito.
Soluzione
Ho capito - non è stato facile, quindi cercherò di spiegare meglio che posso. (per coloro a cui importa)
Codice pertinente del controller:
// _userContentService is IUserContentService
_userContentService.Update(review);
Quindi, il mio controller chiama un metodo chiamato Update
Su IUserContentService
, passando attraverso il titole fortemente Review
oggetto.
Codice pertinente del servizio di contenuto utente
public void Update(Post post)
{
// _userContentRepository is IPostRepository
_userContentRepository.UpdateModel(post);
}
Quindi, il mio servizio chiama un metodo chiamato UpdateModel
Su IPostRepository
, passando attraverso il titole fortemente Review
oggetto.
Ora, ecco la parte difficile.
In realtà l'ho fatto nessun repository specifici. Ho un repository generico chiamato GenericRepository<T> : IRepository<T>
, che gestisce Tutti i diversi repository.
Quindi quando qualcosa richiede a IPostRepository
(cosa che stava facendo il mio servizio), DE lo avrebbe dato a GenericRepository<Post>
.
Ma ora lo do un PostRepository
:
public class PostRepository : GenericRepository<Post>, IPostRepository
{
public void UpdateModel(Post post)
{
var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
}
E perché la classe deriva da GenericRepository, eredita tutta la logica del repository core (trova, aggiungi, ecc.).
All'inizio ho provato a metterlo UpdateModel codice nel GenericRepository classe stessa (e quindi non avrei avuto bisogno di questo repository specifico), ma il problema è la logica per recuperare l'entità esistente si basa su una chiave di entità specifica, che la GenericRepository<T>
non saprei.
Ma il risultato finale è il Cucitura è nascosto in profondità nelle profondità del livello dati e finisco con un controller davvero pulito.
MODIFICARE
Questa "tecnica stub" funziona anche:
public void UpdateModel(Post post)
{
var stub = new Review {PostId = post.PostId};
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
Ma il problema è perché Inviare è astratto, non posso istanziare e quindi dovrei controllare il tipo di post e creare stub per ogni tipo derivato. Non davvero un'opzione.
EDIT 2 (l'ultima volta)
Ok, ho ottenuto la "tecnica stub" che lavora con le classi astratte, quindi ora il problema della concorrenza è risolto.
Ho aggiunto un parametro di tipo generico al mio UpdateModel metodo e lo speciale nuovo () vincolo.
Implementazione:
public void UpdateModel<T>(T post) where T : Post, new()
{
var stub = new T { PostId = post.PostId };
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>, post);
}
Interfaccia:
void UpdateModel<T>(T post) where T : Post, new();
Questo mi impedisce di dover capire manualmente il tipo di T, impedisce i problemi di concorrenza e impedisce anche un viaggio extra sul DB.
Pretty Groovy.
EDIT 3 (ho pensato che l'ultima volta fosse l'ultima volta)
La "tecnica di stub" sopra funziona, ma se recupero prima l'oggetto, lancia un'eccezione indicando un'entità con quella chiave già esiste nell'OSM.
Qualcuno può consigliare come gestirlo?
EDIT 4 (OK - Eccolo!)
Ho trovato la soluzione, grazie a questa risposta: È possibile verificare se un oggetto è già allegato a un contesto di dati in Entity Framework?
Avevo provato a "verificare se l'entità è allegata" usando il seguente codice:
ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);
Ma è sempre tornato nullo, anche quando ho esplorato l'OSM ho potuto vedere la mia entità lì con la stessa chiave.
Ma questo codice funziona:
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)
Forse perché sto usando pure POCO, l'OSM ha avuto problemi a capire la chiave di entità, chissà.
Oh e un'altra cosa che ho aggiunto - in modo da non dover aggiungere un repository specifico per ogni entità, ho creato un attributo chiamato "Entitykey"(Attributo di proprietà pubblica).
Tutti i POCO devono avere 1 proprietà pubblica decorata con quell'attributo, o lancio un'eccezione nel mio modulo repository.
Quindi il mio repository generico cerca quindi questa proprietà per creare/configurare lo stub.
Sì, usa la riflessione, ma è una riflessione intelligente (basata su attributi) e sto già usando la riflessione per la plularizzazione dei nomi di set di entità da T.
Comunque, problema risolto: ora funziona tutto bene!