Design pattern per un progetto ASP.NET utilizzando Entity Framework

2

Sto costruendo un sito Web in ASP.NET (Web Forms) su un motore con regole aziendali (che fondamentalmente si trova in una DLL separata), connesso a un database mappato con Entity Framework (in un terzo progetto separato ).

Ho progettato per primo il Motore, che ha un contesto Entity Framework, e poi ho continuato a lavorare sul sito web, che presenta vari rapporti. Credo di aver commesso un terribile errore di progettazione in quanto il sito web ha il suo contesto (che all'inizio sembrava normale).

Presento questo mockup del motore e il codice di una pagina di report:

Motore (in una DLL separata):

public Engine
{
    DatabaseEntities _engineContext;

    public Engine()
    {
       // Connection string and procedure managed in DB layer
        _engineContext = DatabaseEntities.Connect();
    }

    public ChangeSomeEntity(SomeEntity someEntity, int newValue)
    {
        //Suppose there's some validation too, non trivial stuff
        SomeEntity.Value = newValue;
        _engineContext.SaveChanges();
    }
}

E segnala:

public partial class MyReport : Page
{
    Engine _engine;
    DatabaseEntities _webpageContext;

    public MyReport()
    {
        _engine = new Engine();
        _databaseContext = DatabaseEntities.Connect();
    }

    public void ChangeSomeEntityButton_Clicked(object sender, EventArgs e)
    {
        SomeEntity someEntity;
        //Wrong way:
        //Get the entity from the webpage context
        someEntity = _webpageContext.SomeEntities.Single(s => s.Id == SomeEntityId);
        //Send the entity from _webpageContext to the engine
        _engine.ChangeSomeEntity(someEntity, SomeEntityNewValue); // <- oops, conflict of context

        //Right(?) way:
        //Get the entity from the engine context
        someEntity = _engine.GetSomeEntity(SomeEntityId); //undefined above
        //Send the entity from the engine's context to the engine
        _engine.ChangeSomeEntity(someEntity, SomeEntityNewValue); // <- oops, conflict of context
     }
}

Poiché la pagina web ha il suo contesto, dare al motore un'entità da un contesto diverso causerà un errore. Mi capita di sapere di non farlo, di dare le entità del motore solo dal proprio contesto. Ma questo è un progetto molto incline agli errori. Vedo l'errore dei miei modi ora. Non conosco la strada giusta.

Sto considerando:

  • Creazione della connessione nel motore e trasferimento alla pagina web. Installa sempre un motore, rendi accessibile il suo contesto da una proprietà, condividendolo. Possibili problemi: altri conflitti? Lento? Problemi di concorrenza se voglio espandere ad AJAX?
  • Creazione della connessione dalla pagina Web e trasmissione al motore (credo che si tratti di un'iniezione di dipendenza?)
  • Solo parlando tramite ID. Crea ridondanza, non sempre pratico, suona arcaico. Ma allo stesso tempo, ho già recuperato materiale dalla pagina come ID che devo comunque recuperare.

Quale sarebbe il miglior compromesso qui per sicurezza, facilità d'uso e comprensione, stabilità e velocità?

    
posta MPelletier 06.11.2013 - 21:41
fonte

2 risposte

2

Non penso che tu sia a un milione di miglia lontano da dove devi essere.

Ciò che devi fare in realtà dipende dal livello di astrazione di cui hai bisogno e da ciò che prevedi che il futuro di questa applicazione sia.

Sicuramente non vuoi usare più contesti in una singola richiesta http. Ciò ti causerà molto dolore.

Come minimo vorrei alterare il tuo Motore per avere la dipendenza del contesto iniettata in (attraverso il costruttore o setter). Ciò ti consentirà di isolare il motore a scopo di test iniettando in un contesto mocked.

Vorrei quindi prendere in considerazione la creazione di una classe BasePage che reintroduca un'istanza del motore nel costruttore (passando in un nuovo contesto). Ogni pagina successiva erediterà quindi da BasePage e pertanto avrà un'istanza di Engine per impostazione predefinita.

Lavorerei quindi sulla classe Engine per fornire tutte le funzionalità richieste dalle tue pagine (possibilmente sottoclassing se inizia a diventare un po 'monolitico). Quindi assicurati che tutta l'interazione tra l'interfaccia utente e il database sottostante sia canalizzata attraverso il motore. In questo modo puoi estendere il motore come richiesto, ad esempio Engine.Ajax.Get ... o qualcosa del genere.

Come ho detto, il livello di astrazione richiesto potrebbe rivelare altre considerazioni di progettazione che è necessario apportare. Potresti voler implementare un repository e un modello di unità di lavoro in cima al contesto (tecnicamente un contesto EF ti dà già questo, ma se non sei sicuro di attenersi a EF o sai che dovrai introdurre opzioni di accesso ai dati alternative allora dovrà considerarlo).

Se stai iniettando dipendenze e quelle dipendenze sono "volatili", allora puoi cercare di usare un'inversione del contenitore di controllo come AutoFac o Windsor.

    
risposta data 06.11.2013 - 23:29
fonte
1

Direi che hai un motivo per usare WebForms piuttosto che MVC ma il mio primo suggerimento sarebbe questo: Se puoi, usa ASP.Net MVC.

Bene, toglilo dal mio petto, lascia continuare.

Hai ragione di sospettare che la tua architettura, avendo il tuo archivio entità e poi un secondo modo di accedere direttamente al tuo database quando usi Entity Framework sia un grande no-no. La semplice regola da considerare è questa: Solo un modo per e dal database.

Anche se sei bloccato con una tecnologia stanca come WebForms, puoi ancora prendere una pagina utile dal libro MVC usando l'architettura MVC. Sei a metà strada con il tuo motore, che suona come se stesse facendo gran parte del lavoro del livello Model. In effetti mi sembra che tu voglia andare un po 'oltre e usare un approccio di tipo ViewModel - un ViewModel si comporta come un modello, ma ha cose extra che rendono più facile lavorare dal front-end, quindi si comporta in modo efficace DTO, ma se ci sono proprietà che non vuoi essere in grado di scrivere sul front-end o altra logica relativa alla vista, questa va nella classe ViewModel.

Quindi assicurati che il tuo livello entità possa prendere un oggetto ViewModel (o probabilmente creerai un'interfaccia per le tue entità quindi se avessi un oggetto BookRecord creerai un'interfaccia IBookRecord che l'entità BookRecord implementerebbe e implementerebbe anche BookRecordViewModel : se possibile, è sempre meglio passare attorno alle interfacce e facilita davvero DI, il mocking e tutte quelle cose buone che aiutano a costruire la resilienza in generale.

L'unica vera differenza che ciò comporta è che dovrai gestire la logica per gestire gli aggiornamenti nel tuo motore - quando spingi giù un BookRecordViewModel dall'interfaccia dovrai verificare se ha un campo Id impostato e se esegue query e aggiorna, se non lo aggiunge come nuovo record. Potresti averlo già implementato già nel tuo progetto Engine, nel qual caso questo non dovrebbe presentare alcun problema.

    
risposta data 06.11.2013 - 23:31
fonte

Leggi altre domande sui tag