Settembre 2017

Volume 32 Numero 9

Il presente articolo è stato tradotto automaticamente.

Punti dati - Domain-Driven Design (DDD) più intuitiva in EF Core 2.0

Da Julie Lerman

Julie LermanSe ci ha seguito questa colonna per un periodo di tempo, si possono notare alcuni articoli sull'implementazione di Entity Framework (EF) durante la compilazione di soluzioni che lean modelli la progettazione (DDD) e informazioni aggiuntive. Anche se DDD è incentrata sul dominio, non in modalità di persistenza di dati, a un certo punto è necessario il flusso da e verso il software di dati.

In oltre le iterazioni di Entity Framework, i motivi (convenzionali o personalizzati) hanno consentito agli utenti di eseguire il mapping direttamente al database senza troppa le forze di attrito classi di dominio non complicati. E la Guida è stato in genere che se il livello di mapping di Entity Framework si occupa di ottenere i modelli di dominio ben progettata e verso il database senza dover creare un modello di dati aggiuntivi, questo è sufficiente. Ma nel punto in cui è necessario modificare le classi di dominio e la logica per renderli funzionano meglio con Entity Framework, che è un contrassegno rosso che è possibile creare un modello per la persistenza dei dati e quindi eseguire il mapping da classi del modello di dominio per le classi di modello di dati.

È stato leggermente sorprendente comprendere tempo trascorso tali articoli relativi mapping DDD ed EF ha vinto. È stata quattro anni poiché ho scritto la serie di tre parti chiamata "generazione di codice per la progettazione basati su dominio: Suggerimenti per gli sviluppatori Data-Focused,"che si estendono sui problemi di agosto e settembre o ottobre 2013 di MSDN Magazine. Questo è un collegamento per la prima parte, che include i collegamenti per l'intera serie: msdn.com/magazine/dn342868.

Non vi sono due colonne specifiche che indirizzato modelli DDD e illustrato come EF non sono stati o non facilmente il mapping tra le classi di dominio e il database. A partire da EF6, uno dei problemi più gravi è il fatto che è Impossibile incapsulare e proteggere così una raccolta di "figlio". Tramite schemi noti per la protezione della raccolta (in genere in questo modo em ploying IEnumerable) non allineare con requisiti di EF ed EF non sarebbe anche in grado di riconoscere che la navigazione deve essere parte del modello. Steve Smith e dedicato molto tempo a pensare su questa operazione quando abbiamo creato i corsi Pluralsight, nozioni fondamentali sulla progettazione la (PS/bit.ly-ggg) e infine Steve ha realizzato con una soluzione alternativa nice (bit.ly/ 2ufw89D).

Infine EF Core per risolvere questo problema con la versione 1.1 e ho scritto su questa nuova funzionalità nella colonna gennaio 2017 (msdn.com/magazine/mt745093). EF Core 1.0 e 1.1 anche risolti alcuni altri vincoli DDD ma alcuni spazi vuoti a sinistra, in particolare l'impossibilità di eseguire il mapping di oggetti valore DDD utilizzati per i tipi di dominio. La possibilità di eseguire questa operazione esistesse EF sin dall'inizio, ma non era ancora stato portato a EF Core. Ma con Entity Framework Core 2.0 future, questa limitazione è ora più visibili.

Quello che farò da eseguire in questo articolo è stabilire le funzionalità di Entity Framework Core 2.0 disponibili che allinea con molti dei concetti DDD. Sono molto più semplice per gli sviluppatori che l'utilizzo di questi concetti principali di Entity Framework 2.0 e probabilmente introdurrà ad essi per la prima volta. Anche se non si intende adottare DDD, è possibile trarre comunque vantaggio dai relativi molti modelli di grande! E ora è possibile eseguire tale ancora maggiori con Entity Framework di base.

Ottiene uno a uno più intelligente

Nel suo libro, "La progettazione," Eric Evans è indicato "un'associazione bidirezionale significa che entrambi gli oggetti possono essere derstood di annullare solo insieme. Quando i requisiti dell'applicazione non viene chiamato per l'attraversamento in entrambe le direzioni, aggiunta di un attraversamento di-rection interdipendenza riduce e semplifica la progettazione". Seguendo questa Guida, gli effetti collaterali è rimosso effettivamente nel codice. Entity Framework è sempre stato in grado di gestire le relazioni unidirezionale con uno-a-molti e uno a uno. In realtà, durante la scrittura di questo articolo, appreso che my malinteso longtime che una relazione uno a uno con entrambi termina forza obbligatorio è in una relazione bidirezionale è errato. Tuttavia, è stato necessario configurare in modo esplicito tali l'IM-tionships necessarie e che è un elemento che non è necessario utilizzare ora in Entity Framework Core, ad eccezione dei casi limite.

Un requisito sfavorevole in EF6 per le relazioni uno a uno stato che la proprietà di chiave nel tipo dipendente doveva doppio come chiave esterna all'entità di entità. È stato di progettazione di classi in un modo dispari, anche se è stato ottenuto utilizzato a esso. Grazie all'introduzione del supporto per le chiavi esterne univoche in Entity Framework Core, è ora possibile avere una proprietà di chiave esterna esplicita nell'entità finale dipendente della relazione uno a uno. Disporre di una chiave esterna esplicita è più naturale. E nella maggior parte dei casi, i Core EF deve essere in grado di dedurre correttamente l'entità finale dipendente della relazione in base alla presenza di tale proprietà di chiave esterna. Se non viene eseguita correttamente a causa di un caso limite, sarà necessario aggiungere una configurazione, che verrà illustrata a breve quando si rinomina la proprietà di chiave esterna.

Per dimostrare una relazione uno a uno, verrà utilizzato il dominio principale EF preferito: le classi del filmato "Rai Samu sette":

public class Samurai {
  public int Id { get; set; }
  public string Name { get; set; }
  public Entrance Entrance { get; set; }
}

public class Entrance {
  public int Id { get; set; }
  public string SceneName { get; set; }
  public int SamuraiId { get; set; }
}

Ora con Entity Framework Core, questa coppia di classi, Samurai ed entrata (del carattere prima occorrenza del filmato), verrà identificato correttamente come una relazione uno a uno unidirezionale con entrata da tipo dipendente. Non è necessario includere una proprietà di navigazione in ingresso e non è necessario alcun mapping speciale nell'API Fluent. La chiave esterna (SamuraiId) segue convenzione, in modo EF Core è in grado di riconoscere la relazione.

Core EF deduce che nel database, Entrance.SamuraiId è una chiave esterna univoca che punta al Samurai. Tenere presente un elemento opinione con perché (come continuamente è necessario ricordare per Me), Core EF non EF6! Per impostazione predefinita, .NET e componenti di base di EF considererà il Samurai.Entrance come una proprietà facoltativa in fase di esecuzione, a meno che non si dispone di logica di dominio in grado di applicare tale entrata è obbligatoria. A partire da EF4.3, è necessario il vantaggio della convalida API che avrebbe risposto a un'annotazione [obbligatorio] nella classe o il mapping. Ma non vengono convalidati (ancora)? API principali di EF da controllare per quel particolare problema. E vi sono altri requisiti che sono correlati al database. Ad esempio, Entrance.SamuraiId sarà un valore int non nullable. Se si tenta di inserire un ingresso senza un valore SamuraiId popolato, Core EF non intercettare i dati non validi, che significa anche che il provider InMemory attualmente non reclamano. Ma nel database relazionale deve generare un errore per il conflitto di vincoli.

Da una prospettiva DDD, tuttavia, questo non è realmente un problema perché non affidarsi al livello di persistenza per segnalare errori nella logica del dominio. Se il Samurai richiede un ingresso, che è una regola business. Se è possibile avere entrate o phaned, che è anche una regola business. Pertanto, la convalida deve essere comunque parte della logica del dominio.

Per i casi di edge suggerito in precedenza, ecco un esempio. Se la chiave esterna nell'entità dipendenti (ad esempio, entrata) non segue una convenzione, è possibile utilizzare l'API Fluent per informare EF Core. Se è stata Entrance.SamuraiId, ad esempio Entrance.SamuraiFK, si può aiutare a chiarire la chiave esterna tramite:

modelBuilder.Entity<Samurai>().HasOne(s=>s.Entrance)
  .WithOne().HasForeignKey<Entrance>(e=>e.SamuraiFK);

Se la relazione è obbligatorio per entrambe le estremità (ovvero, entrata deve avere un Samurai) è possibile aggiungere IsRequired dopo WithOne.

Proprietà possono essere ulteriormente incapsulate

DDD descrive per compilare le aggregazioni (oggetti grafici), dove la radice di aggregazione (l'oggetto principale nel grafico) nel controllo di tutti gli altri oggetti nel grafico. Ciò significa che la scrittura di codice che impedisce al codice altri impropriamente o anche utilizzino le regole. Che incapsula le proprietà, quindi non possono essere in modo casuale impostazione (e, spesso, in modo casuale in lettura) è un metodo chiave per la protezione di un grafico. In EF6 e versioni precedenti, è sempre possibile apportare scalari e le proprietà di navigazione sono Setter privata e comunque riconosciute da Entity Framework quando di lettura e di ottenere dati aggiornati, ma è Impossibile apportare facilmente le proprietà private. Un post Miller Rowan Mostra un modo per farlo in EF6 e collegamenti a alcune soluzioni precedenti (bit.ly/2eHTm2t). E non era true per proteggere una raccolta di navigazione in una relazione uno-a-molti. Informazioni su questo problema quest'ultimo è stato scritto molto. A questo punto, non solo è possibile ottenere facilmente lavoro EF Core con proprietà privata che dispongono di supporto per i campi (o dedotti campi sottostanti), ma è anche realmente può incapsulare le proprietà della raccolta, grazie al supporto per il mapping di IEnumerable < T >. Ho scritto sui campi sottostanti e IEnumerable < T > nell'articolo menzionato in precedenza gennaio January 2017, pertanto non rehash qui i dettagli. Tuttavia, è molto importante modelli DDD e pertanto rilevante notare in questo articolo.

Mentre è possibile nascondere i valori scalari e le raccolte, è un altro tipo di proprietà è molto bene per incapsulare, ovvero le proprietà di navigazione. Le raccolte di navigazione è vantaggioso il supporto di IEnumerable < T >, ma le proprietà di navigazione che sono private, ad esempio Samurai.Entrance, non è possibile comprendere dal modello. Tuttavia, è possibile configurare il modello da includere una proprietà di navigazione che è nascosta di radice.

Ad esempio, nel codice seguente dichiarato entrata come una proprietà privata di Samurai (e non anche utilizzando un campo sottostante esplicita, anche se è stato possibile se necessario). È possibile creare una nuova entrata con il metodo CreateEntrance (che chiama un metodo factory in entrata) ed è possibile leggere solo a proprietà SceneName la voce dell'ingresso. Si noti che è che utilizza l'operatore condizionale null c# 6 per evitare un'eccezione se sono stati ancora caricati all'ingresso:

private Entrance Entrance {get;set;}
public void CreateEntrance (string sceneName) {
    Entrance = Entrance.Create (sceneName);
  }
public string EntranceScene => Entrance?.SceneName;

Per convenzione, Core EF non rendere presume su questa proprietà privata. Anche se il campo sottostante aveva all'ingresso privato non avrebbe individuato automaticamente e non sarà possibile utilizzarlo quando si interagisce con l'archivio dati. Si tratta di una progettazione API intenzionale per garantire la protezione da potenziali effetti collaterali. Ma è possibile configurare in modo esplicito. Tenere presente che quando entrata è pubblico, EF Core è in grado di comprendere la relazione uno a uno. Tuttavia, in quanto privato è prima necessario essere certi che EF SA su questo.

OnModelCreating, è necessario aggiungere il mapping di Microsoft Office fluent HasOne/WithOne per rendere compatibile con Entity Framework Core. Poiché l'entrata è privato, è possibile utilizzare un'espressione lambda come un parametro di HasOne. In alternativa, è necessario descrivono la proprietà per il tipo e il relativo nome. WithOne accetta in genere un'espressione lambda per specificare la proprietà di navigazione a altra estremità dell'associazione. Ma entrata non è una proprietà di navigazione Samurai, ma solo la chiave esterna. Va bene! È possibile lasciare vuoto il parametro perché EF Core dispone ora di informazioni sufficienti per capire:

modelBuilder.Entity<Samurai> ()
  .HasOne (typeof (Entrance), "Entrance").WithOne();

Cosa accade se utilizzare una proprietà di backup, ad esempio _entrance nella classe Samurai, come illustrato in queste modifiche:

private Entrance _entrance;
private Entrance Entrance { get{return _entrance;} }
public void CreateEntrance (string sceneName) {
    _entrance = _entrance.Create (sceneName);
  }
public string EntranceScene => _entrance?.SceneName;

Core EF verrà scoprire che è necessario utilizzare il campo sottostante durante la materializzazione della proprietà di ingresso. Questo avviene perché, come spiegato Arthur Vickers nella conversazione molto abbiamo su GitHub mentre è dato dal fatto di apprendimento, se "è un campo sottostante ed è presente alcuna funzione set, EF utilizza solo il campo sottostante [] non essendo nient'altro può utilizzare." Pertanto, continua comunque a funzionare.

Se il nome del campo sottostante non segue convenzione, se, ad esempio, è denominato _foo, è necessario un metadati-configurazione:

modelBuilder.Entity<Samurai> ()
  .Metadata
  .FindNavigation ("Entrance")
  .SetField("_foo");

Ora gli aggiornamenti per le query e il database sarà in grado di risolvere tale relazione. Tenere presente che se si desidera utilizzare il caricamento non differito, è necessario utilizzare una stringa per becaise di ingresso che non può essere individuato dall'espressione lambda; Per esempio:

var samurai = context.Samurais.Include("Entrance").FirstOrDefault();

È possibile utilizzare la sintassi standard per l'interazione con il backup di campi per elementi come i filtri, come illustrato nella parte inferiore della pagina di documentazione backup campi bit.ly/2wJeHQ7.

Gli oggetti di valore sono ora supportati

Gli oggetti di valore sono un concetto importante per DDD poiché consentono di definire i modelli di dominio come tipi di valore. Un valore ob-ject non ha la propria identità e diventa parte dell'entità che viene utilizzato come una proprietà. Considerare il tipo di valore di stringa, che è costituito da una serie di caratteri. Perché se si modifica un solo carattere, il significato della parola, le stringhe sono immutabili. Per modificare una stringa, è necessario sostituire l'oggetto stringa intera. DDD consente da prendere in considerazione valore utilizzando gli oggetti in qualsiasi punto che è stata individuata una relazione uno a uno. È possibile approfondire valore ob-progetti in corso Nozioni fondamentali su DDD che già indicato in precedenza.

EF sempre la possibilità di includere gli oggetti di valore tramite il relativo tipo di ComplexType è supportata. È possibile definire un tipo senza chiave e usare tale tipo come una proprietà di un'entità. Che è stato sufficiente per trigger EF per lo riconosce come un elemento ComplexType e mappare le proprietà della tabella a cui viene eseguito il mapping di entità. È quindi possibile estendere il tipo di cui dispongono anche di funzionalità quired ripetizione di un oggetto di valore, ad esempio assicurando che il tipo è non modificabile e un mezzo per valutare tutte le proprietà per determinare l'uguaglianza e l'Hash viene sottoposto a override. Tipi spesso derivano dalla classe di base ValueObject di Jimmy Bogard adottare rapidamente questi attributi.

Nome di una persona è un tipo che viene comunemente usato come un oggetto valore. È possibile garantire che ogni volta che un utente desidera avere un nome di persona in un'entità, essi seguono sempre un set comune di regole. Figura 1 viene illustrata una semplice classe PersonName che dispone di proprietà di prima e ultima, sia completamente incapsulato, nonché una proprietà per restituire un nome completo. La classe è progettata per garantire che sempre fornite entrambe le parti del nome.

Figura 1 oggetto PersonName valore

public class PersonName : ValueObject<PersonName> {
  public static PersonName Create (string first, string last) {
    return new PersonName (first, last);
  }
  private PersonName () { } 
  private PersonName (string first, string last) {
    First = first;
    Last = last;
  }
  public string First { get; private set; }
  public string Last { get; private set; }
  public string FullName => First + " " + Last;
}

Sono in grado utilizzare PersonName come proprietà in altri tipi e continuare a arricchita logica aggiuntiva nella classe PersonName. Il vantaggio dell'oggetto valore su una relazione uno a uno qui è che non è necessario mantenere la relazione, quando si consapevole di codifica. Si tratta di programmazione orientata a oggetti standard. È semplicemente un'altra proprietà. Nella classe Samurai, ho aggiungere una nuova proprietà di questo tipo, reso privato il setter e fornito un altro metodo, denominato identificare usare invece il metodo di impostazione:

 

public PersonName SecretIdentity{get;private set;}
public void Identify (string first, string last) {
  SecretIdentity = PersonName.Create (first, last);
}

Fino a EF Core 2.0, si è verificato alcuna funzionalità simili per complexType, pertanto è Impossibile usare facilmente gli oggetti di valore senza aggiungere un'operazione in un modello di dati separato. Anziché semplicemente reimplementare ComplexType in Entity Framework Core, il team EF creato un concetto definito di proprietà di entità, che utilizza un'altra funzionalità di Entity Framework Core, nascondere le proprietà. A questo punto, le proprietà di entità sono riconosciute come proprietà aggiuntive dei tipi che proprio them ed EF Core riconosce come risolvono nello schema del database e come compilare query e aggiornamenti che rispettano i dati.

Convenzione di Core 2.0 EF non rileverà automaticamente che la nuova proprietà SecretIdentity è un tipo da incorporare nei dati persistenti. È necessario indicare in modo esplicito l'elemento DbContext che la proprietà Samurai.SecretIdentity è un'entità di proprietà utilizzando il metodo OwnsOne DbContext.OnModelCreating:

protected override void OnModelCreating (ModelBuilder modelBuilder) {
  modelBuilder.Entity<Samurai>().OwnsOne(s => s.SecretIdentity);
}

In questo modo le proprietà di PersonName risolvere come proprietà di Samurai. Mentre il codice verrà utilizzare il tipo Samurai.SecretIdentity ed esplorare che per il primo e l'ultima proprietà, queste due proprietà verrà risolto come colonne nella tabella di database Samurais. Convenzione di Core EF verrà assegnare loro un nome con il nome della proprietà in Samurai (SecretIdentity) e il nome della proprietà dell'entità di proprietà, come illustrato nella figura 2.

Lo Schema della tabella Samurais, incluse le proprietà del valore
Figura 2, lo Schema della tabella Samurais, incluse le proprietà del valore

Ora è possibile identificare nome segreto del Samurai un e salvarlo con un codice simile al seguente:

using (var context = new SamuraiContext()) {
  var samurai = new Samurai { Name = "HubbieSan" 
  samurai.Identify ("Late", "Todinner");
  context.Samurais.Add (samurai);
  context.SaveChanges ();
}

Nell'archivio dati, "Tardiva" Ottiene persistenti nel campo SecretIdentity_First e "Todinner" nel campo SecretIdentity_Last.

Quindi si è semplicemente esegue una query per un Samurai:

var samurai=context.Samurais .FirstOrDefaultAsync (s => s.Name == "HubbieSan")

Core EF garantisca che proprietà SecretIdentity del Samurai risultante viene popolata e che è possibile vedere richiedendo l'identità:

samurai.SecretIdentity.FullName

EF Core richiede che le proprietà che appartengono a entità vengono popolate. Nel download di esempio, si noterà modalità di progettazione di tipo per contenere che PersonName.

Classi semplici di lezioni semplice

Ciò che ho illustrato che seguito sono classi semplici che alcuni dei concetti di base di un'implementazione DDD sfruttano in modo più minimo che consente di vedere come Core EF risponde a questi costrutti. Si è visto che è in grado di comprendere le relazioni uno a uno di unidirezionale EF Core 2.0. È possibile rendere persistenti i dati da entità in cui sono incapsulate completamente proprietà scalari, navigazione e raccolta. Inoltre, consente di utilizzare oggetti nel modello di dominio e sia in grado di mantenere tali valori, nonché.

Per questo articolo è ho mantenuta classi semplici e privi di logica aggiuntiva che vincola più correttamente le entità e oggetti di valore in base a modelli DDD. Questa semplicità viene riflessa nell'esempio di download, è anche in GitHub all'indirizzo bit.ly/2tDRXwi. Qui è disponibile la versione semplice e un ramo avanzato in cui ho ristretta questo modello di dominio verso il basso e applicate alcune procedure DDD supplementari per la radice di aggregazione (Samurai), l'entità correlata (entrata) e l'oggetto di valore (PersonName) in modo da visualizzare come Core EF 2.0 gestisce un'espressione di una funzione di aggregazione DDD più realistica. Nei prossimi articoli, verrà illustrato i modelli avanzati applicati in quel ramo.

Tenere presente che se si utilizza una versione di Entity Framework Core 2.0 poco prima la versione finale. Mentre la maggior parte dei comportamenti che ho disposti a tinta unita, è ancora presente la possibilità di modifiche di lieve entità prima del rilascio di 2.0.0.


Julie Lerman è direttore regionale Microsoft, Microsoft MVP, responsabile del team di software e consulente vive massimi di Vermont. È possibile trovare la presentazione di accesso ai dati e altri argomenti in gruppi di utenti e conferenze tutto il mondo. Blog di utente e su indirizzo thedatafarm.com ed è l'autore di "Programmazione di Entity Framework", nonché un Code First e un'edizione DbContext, tutto da o ' Reilly Media. Seguire proprio su Twitter: @julielerman e vedere proprio corsi Pluralsight PS/juliel.me-video.


Viene illustrato in questo articolo nel forum di MSDN Magazine