EF Code First, MVVM ... e le entità che viaggiano da una parte all'altra

rated by 0 users
This post has 3 Replies | 3 Followers

Top 10 Partecipanti
Maschio
Post 32
Punteggio 718
neronotte Posted: 07-04-2013 17.46

Ciao a tutti,

sto implementando una semplicissima applicazione WPF con MVVM ed Entity Framework come Data Layer. Ho le mie belle viste, con i loro view model, dove serve creo un data context e leggo / scrivo dati, in modo molto, molto, molto banale.

Problema: I miei viewmodel sono disaccoppiati tra loro, e si parlano tramite scambio di messaggi. Ognuno ha il suo datacontext, ma "capita" che si passino degli oggetti... ad esempio, considerate il seguente modello:

class Evento {
    int Id { get; set; }
    string Nome { get; set; }
}

class Movimento {
    int Id { get; set; }
    string Nome { get; set; }
    Evento Evento { get; set; }
}

gli Id sono interi autoincrement. In un tipico use case come il seguente:

"Una volta selezionato un evento, un utente può creare un nuovo movimento associato all'evento stesso"

Il comando "crea nuovo movimento" che si trova sull'EventoViewModel, non fa  nient'altro che lanciare un messaggio di tipo "NuovoMovimentoMessage" passando l'evento stesso (oggetto di dominio) come parametro.

Questo messaggio viene intercettato da un altro view model che consente all'utente di editare i dati del movimento, e salvarli. Tra le proprietà del movimento, c'è ovviamente un riferimento all'evento che l'ha creato... come si vede dal modello in alto.

Quando vado a salvare il movimento, col classico ctx.Movimenti.Add(movimento), EF mi crea automaticamente una copia dell'evento, e questo perché anche l'evento viene visto dal Change Tracker in stato Added.

Ovviamente questo non è un comportamento corretto... mi sarei aspettato, visto che l'evento ha un Id autoincrement diverso da 0, che EF si limitasse a creare un link tra i due oggetti :(

L'unica soluzione che ho trovato è stata quella di effettuare un Attach esplicito dell'Evento al contesto su cui eseguo la creazione del movimento... ma la cosa mi piace poco, perché:

  1. è difficile da astrarre (usare la reflection?)
  2. da fare a mano, se gli oggetti sono parecchio interconnessi tra loro, non è banale

Dove sto sbagliando? A naso, mi sembra troppo strano come behavior di default. è possibile che un comportamento del genere non sia overridable in qualche modo?
Voi che tipo di approccio adottate, di solito?

Grazie,
neronotte 

en → it
int ID {get; set ; }
Top 10 Partecipanti
Post 160
Punteggio 2.850

Non conosco abbastanza EF per poterti dire quale sia la soluzione lato ORM, ma in generale potresti provare a cambiare approccio perché stai introducendo un comportamento che alla lunga potrebbe essere un problema: la gestione della "conversazione".

C'è un lunga digressione sul problema qui:

http://blogs.ugidotnet.org/topics/archive/0001/01/01/conversazione-ldquonon-solo-overviewrdquo-ltsemi-cit.gt.aspx

Sintetizzando molto direi che è "sbagliato" (prendi sbagliato con le pinze) che il secondo ViewModel gestisca in autonomia la persistenza con EF, a naso direi che è responsabilità del chiamante decidere come, cosa e quando salvare.

.m

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 32
Punteggio 718

Ciao Mauro,

grazie per la risposta!

L'idea di dover gestire il salvataggio dell'oggetto che sto inserendo, e la lettura di tutte le entità ad esso correlate, all'interno del medesimo DbContext sinceramente mi piace poco: è un'applicazione Client, che stavo strutturando come Single Page Application, per cui mi sembrava naturale che fosse l'editor stesso a gestire la persistenza del dato (anche perché eventuali errori in scrittura devo comunque mostrarli sulla finestra dell'editor... ma questo lo gestisco tranquillamente con le callback).

Ci sono alcuni dati che leggo una volta, chiudo la transazione, e poi li tengo in locale fino a quando non mi va di refresharli (passami il termine :) ). Alla fin fine mi servono solo per alimentare le relazioni, non faccio denormalizzazioni o quant'altro, per cui non ho grossi problemi di consistenza... soprattutto considerando che sarà una semplice applicazione client monoutente.

Il caso tipico è il seguente: quando creo o modifico un'entità, tengo traccia della data di creazione e della data di modifica, nonché dell'utente che l'ha creata / modificata. Il riferimento all'utente lo prendo al momento del login all'app, e lo salvo nello stato dell'applicazione stessa in modo da non dover re-interrogare il DB ogni volta.

Tu come ti muoveresti in questo caso? Faresti di nuovo una query al DB per rileggere l'utente?

 

_n

 

 

 

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 253
Punteggio 3.478

Non conosco nemmeno io tantissimo Entity Framework, ma forse quello che ti serve lato ORM è avere un SaveOrUpdate perchè da quello che ricordo se chiami Add per lui è una nuova entità. Si trovano in giro estensioni che permettono questo.

 

 public static void SaveOrUpdate<TEntity>

            (this ObjectContext context, TEntity entity)

            where TEntity : class

        {

            ObjectStateEntry stateEntry = null;

            context.ObjectStateManager

                .TryGetObjectStateEntry(entity, out stateEntry);

            var objectSet = context.CreateObjectSet<TEntity>();

            if (stateEntry == null || stateEntry.EntityKey.IsTemporary)

            {

                objectSet.AddObject(entity);

            }

 

            else if (stateEntry.State == EntityState.Detached)

            {

                objectSet.Attach(entity);

                context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);

            }

        }

 

        public static ObjectContext GetObjectContext(this DbContext c)

        {

            return ((IObjectContextAdapter)c).ObjectContext;

        }

 

Lo usi in questo modo 

 Context.GetObjectContext().SaveOrUpdate(u);

In generale il problema di avere diretto accesso al contesto dai ViewModel alla lunga secondo me vanifica i vantaggi. Io preferisco sempre fare uno strato di servizio e non gestire i contesti / Session direttamente nella UI. 

Alk.

  • | Punteggio Post: 5
Pagina 1 di 1 (4 elementi) | RSS
Powered by Community Server (Commercial Edition), by Telligent Systems