Novembre 2016

Volume 31 Numero 11

Il presente articolo è stato tradotto automaticamente.

Cutting Edge - Code First e inizializzazione del database

Da Dino Esposito | Novembre 2016

Dino EspositoSebbene il termine "DevOps" è relativamente nuovo e ha recentemente acquisito in modo da includere molte altre attività, distribuzione e test automatizzati in particolare, ritengo che il primo esempio di un'operazione per gli sviluppatori automatizzabile è come precedente del software stesso. Si fa riferimento alla possibilità di creare e inizializzare il database durante l'installazione dell'applicazione specifica. Molte società produttrici sviluppano sistemi verticali che vengono venduti a un'ampia gamma di clienti e adattati alle proprie esigenze.

Gli aspetti che è possono personalizzare dipendono dalle caratteristiche del prodotto e il dominio aziendale, ma dovrei dire che minimo qualsiasi applicazione software verticale deve disporre di un database specifiche del cliente. Pertanto, il database deve essere creato con le tabelle e gli schemi necessari per il contesto e popolati con dati ad hoc.

Non tutte le attività necessarie possono sempre essere automatizzate e integrate nel prodotto stesso. Si supponga, ad esempio, l'importazione dei dati esistenti. Se i dati da importare si trovano nel file di Excel o database legacy, disparità sono che deve essere creata un'utilità di importazione in un modo per elaborare i dati e caricarli in nuova risorsa di archiviazione. Tuttavia, se è utilizzata Entity Framework 6. x Code First nel livello di accesso ai dati dell'applicazione, quindi almeno la creazione degli schemi di database e tabelle è semplice da automatizzare e possono verificarsi in modo trasparente alla prima esecuzione dell'applicazione.

In questo articolo, verrà fornito un riepilogo alcune funzionalità che sono stati disponibili in Code First sin dagli albori dalla prospettiva di un'applicazione a più clienti. In particolare, mi concentrerò su come creare e compilare un database e come definire a livello di codice la relativa stringa di connessione e nome. 

Layout di base degli schemi di tabella

Si supponga di che avere un nuovo progetto di Visual Studio già collegato al pacchetto NuGet di Entity Framework 6. x. Il passaggio successivo che si consiglia di adottare consiste nel creare una libreria di classi di accesso ai dati o almeno una cartella nel progetto corrente per salvare tutti i file che in qualche modo contribuire con il funzionamento della funzionalità di accesso ai dati. In base alle regole di Entity Framework, è necessario disporre di una classe DbContext che rappresenta il punto di ingresso nel sottosistema di archiviazione dei dati dell'applicazione. Di seguito è riportato un esempio di una classe di specifiche dell'applicazione:

public class RegionsContext : DbContext
{
  ...
}

Per gli occhi dell'applicazione, la classe derivata da DbContext è non più e non sia minore del database. La classe deve esporre un numero limitato di proprietà di DbSet < T >, uno per ogni raccolta di entità viene gestita dall'applicazione. Una proprietà di DbSet < T > è logicamente equivalente a una tabella in un database fisico:

public class RegionsContext : DbContext
{
  public DbSet<Region> Regions { get; set; }
}

Il risultato finale del frammento di codice qui consiste nell'abilitare l'applicazione con un database relazionale contenente una tabella denominata aree di lavoro. Per quanto riguarda lo schema della tabella? Lo schema è determinato dal layout pubblici della classe di area. Il codice prima offre una gamma di attributi e sintassi intuitiva per definire un mapping tra le proprietà della classe e le colonne della tabella sottostante. Nello stesso modo, è possibile definire indici, chiavi primarie, le colonne identity, i valori predefiniti e qualsiasi altra al database sottostante consente di configurare in tabelle e colonne. Di seguito è una versione minima della classe di area:

public class Region
{
  public Region()
  {
    Languages = "EN";
  }
  [Key]
  public string RegionCode { get; set; }
  public string RegionName { get; set; }
  public string Languages { get; set; }
  ...
}

Ovvero, tutti i record nella tabella aree saranno presenti tre colonne di nvarchar (max) (RegionCode, lingue e RegionName) e RegionCode verrà impostato come colonna chiave primaria. Inoltre, qualsiasi istanza della classe di area appena creato avrà il valore "EN" impostare la proprietà di lingue. Questo è un modo per assicurarsi che "EN" è il valore predefinito per la colonna ogni volta che viene aggiunto un nuovo record tramite il codice. Si noti tuttavia che l'impostazione di un valore nel costruttore di una soluzione di Code-First come succede qui non aggiunge automaticamente qualsiasi associazione del valore predefinito nella configurazione del database sottostante.

Denominazione del Database

In una soluzione di Code First, tutte le connessioni al database passano attraverso la classe derivata da DbContext, che apre e chiude le connessioni in modo appropriato l'esposizione ancora pubblicamente la connessione come una proprietà per il codice di assumere il controllo completo sulle operazioni di aperte e chiusura. Per quanto riguarda i dettagli della stringa di connessione e, più importante, come è fornire dettagli in modo parametrico?

Quando si crea una classe derivata DbContext, è necessario fornire un costruttore. Ecco un esempio molto comune:

public RegionsContext(string conn) : base(conn)
{
  ...
}

La classe DbContext ha un costruttore che accetta la stringa di connessione come parametro, pertanto la cosa più semplice da fare è eseguire il mirroring la funzionalità del costruttore sottostante tramite il costruttore della classe derivata. In modo intelligente, la classe DbContext contiene la logica per elaborare la stringa passata. Si presuppone che qualsiasi stringa passati che ha il formato nome = XXX indica che è possibile trovare la stringa di connessione nella voce XXX all'interno della sezione connectionstrings del file di configurazione dell'applicazione (ovvero Web. config per un progetto Web). In caso contrario, qualsiasi stringa passata è considerato il nome del database da creare. In questo caso, ulteriormente i dettagli della stringa di connessione, ad esempio le credenziali e la posizione di server, si prevede siano nel blocco defaultConnectionFactory della sezione entityFramework nel file di configurazione. Si noti che ogni volta che si aggiunge il pacchetto di Entity Framework a un progetto di Visual Studio, il file di configurazione viene modificato automaticamente per supportare la sezione entityFramework. Figura 1 viene riportato l'elenco correlato, leggermente modificata per maggiore chiarezza.

Figura 1 esempio File Web. config come modificata per supportare primo codice di Entity Framework

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="entityFramework"
      type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection,..." />
  </configSections>
    <startup>
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <connectionStrings>
    <!-- Your explicit connection strings -->
  </connectionStrings>
  <entityFramework>
    <defaultConnectionFactory type=
      "System.Data.Entity.Infrastructure.SqlConnectionFactory,
      EntityFramework">
    <parameters>
    <parameter value="Data Source=(local); Integrated Security=True;" />
    </parameters>
  </defaultConnectionFactory>
  <providers>
    <provider invariantName="System.Data.SqlClient"
      type="System.Data.Entity.SqlServer.SqlProviderServices, ..." />
    </providers>
  </entityFramework>
</configuration>

La maggior parte degli esempi che disponibili su Code First si basano su una stringa di connessione fisso e costante a cui fa riferimento dal file di configurazione o passato in modo esplicito la classe del contesto. Il risultato finale è che Code First crea il database utilizzando l'ora della prima stringa di connessione fornita che l'applicazione viene eseguita. Verrà ora approfondita un po' questo aspetto.

La classe DbContext supporta quattro strategie di inizializzazione, come elencato Figura 2.

Figura 2 Database Code First inizializzazione strategie

Strategia Descrizione
CreateDatabaseIfNotExists

Controlla se il database esistente e lo crea se non viene trovata una. Se il database esiste ma ha uno schema incompatibile genera un'eccezione.

Nota: Questo è l'inizializzatore predefinito.

DropCreateDatabaseIfModelChanges Crea il database se non esiste. Se il database esiste ma ha uno schema incompatibile quindi Elimina il database esistente e crea uno nuovo.
DropCreateDatabaseAlways Ogni volta che si esegue l'applicazione elimina un database esistente e ricreato.
Inizializzatori personalizzati

Classe di inizializzatore personalizzato che scritto da usare out semplicemente il comportamento che si desidera che nessuna delle altre offerte di opzioni.

Nota: Utilizzare questa opzione per aggiungere un contenuto master nel database.

In base al comportamento predefinito CreateDatabaseIfNotExists, ogni volta che viene creata la classe del contesto, controlla se il database di riferimento esiste ed è raggiungibile. In caso contrario, viene creata. Se il database esista e sia raggiungibile ma non dispone di uno schema compatibile con il layout pubblico delle classi di entità, viene generata un'eccezione. Per rimuovere l'eccezione, è necessario modificare la classe di entità o, più probabilmente, modificare lo schema del database tramite l'interfaccia o uno script di migrazione di Entity Framework di programmazione di database.

Trovare la soluzione ideale per questa opzione quando l'applicazione raggiunge il livello di produzione. Durante la fase di sviluppo, tuttavia, preferisco l'opzione DropCreateDatabaseIfModelChanges, che è essenzialmente protegge da qualsiasi attività di manutenzione database: È sufficiente è modificare le classi di entità come appropriato ed Entity Framework consente di correggere il database la volta successiva che si preme F5 in Visual Studio. Per attivare la strategia di inizializzazione di propria scelta aggiungere la riga seguente al costruttore della classe DbContext personalizzata:

Database.SetInitializer<YourDbContext>(
  new DropCreateDatabaseIfModelChanges<YourDbContext>());

Si noti che è inoltre possibile impostare l'inizializzatore del database nel file di configurazione, potrebbe essere una buona idea se si prevede di utilizzare diverse strategie di sviluppo e produzione.

In sintesi, Code First consente di scrivere un'applicazione che crea automaticamente tutte le tabelle del database alla prima esecuzione. In altre parole, è necessario effettuare è copiare i file e i file binari e quindi avviarla. Questo comportamento, tuttavia, funziona le migliori se il sistema viene creato per un singolo cliente. Quando si dispone di un sistema a più clienti, la migliore è possibile eseguire è utilizzare un'utilità di installazione.

Uno dei motivi per adottare un approccio leggermente diverso è che si desidera denominare il database in modo diverso; potrebbe ad esempio, si desidera aggiungere un prefisso specifiche del cliente al nome. Figura 3 Mostra la struttura di questo tipo un'utilità della riga di comando. Il programma richiede il prefisso del cliente dalla riga di comando, formatta il nome del database come appropriato e quindi attiva la classe derivata DbContext che ricrea il database e la riempie con dati iniziali appropriato.

Figura 3 specifiche del cliente nome del Database

class Program
{
  static void Main(string[] args)
  {
    var prefix = args[0];
                // boundary checks skipped
    var dbName = String.Format("yourdb_{0}", prefix);
    using (var db = new YourDbContext(dbName))
    {
      // Fill the database in some way
    }
  }
}

Riempimento iniziale del Database

Qualsiasi sistema progettato per soddisfare le esigenze di più clienti nello stesso dominio aziendale deve avere un numero di tabelle dedicate alla memorizzazione delle opzioni e le preferenze che differiscono da cliente a cliente. In qualche modo durante l'installazione del software, è necessario specificare queste informazioni. Realisticamente, parte del carico iniziale del database è condiviso da tutte le installazioni, ma una parte è specifiche del cliente. La parte che dipende dai dati del cliente viene in genere importata da origini esterne e richiede una routine ad hoc, che si tratti di uno script di qualche tipo o codice compilato. A seconda del contesto, potrebbe anche risultare poco fuori luogo il concetto di un meccanismo di inserimento delle dipendenze per generalizzare la struttura di unità di importazione all'interno di utilità di installazione che consente di inizializzare il database. Per quanto riguarda il contenuto statico di database, tuttavia, Code First è servizi ad hoc da offrire.

Inizializzatori personalizzati

Per i dati contenuti nel database durante il processo di inizializzazione, è necessario creare un inizializzatore di database personalizzata come descritto in Figura 2. Un inizializzatore personalizzato è una classe che eredita da uno degli inizializzatori predefiniti, ad esempio DropCreateDatabaseIfModelChanges. Il requisito obbligatorio solo per questa classe esegue l'override del metodo Seed:

public class YourDbInitializer : DropCreateDatabaseAlways<YourDbContext>
{
  protected override void Seed(YourDbContext context)
  {              
    ...
  }
}

Nell'implementazione del metodo Seed, si esegue qualsiasi codice che consente di popolare le tabelle del database utilizzando DbContext fornito per l'accesso. Ovvero tutti gli elementi.

Se si intende un'applicazione a più clienti, la definizione di un inizializzatore personalizzato è una mossa che offre un singolo punto per lo stato attivo alla forma il form iniziale del database su una base per ogni cliente. L'inizializzatore è una classe c# normale, pertanto, potrebbe essere abilitata utilizzando gli strumenti di inserimento delle dipendenze per connetterla a parti specifiche di logica che è possibile importare dati da solo luogo di residenza.

Infine, gli inizializzatori di database possono essere disabilitati completamente in modo che il programma di installazione del database rimane un'operazione completamente distinta, forse anche gestiti da un altro IT o team DevOps. Per indicare al framework Code First di ignorare tutti gli inizializzatori, è necessario eseguire codice nel costruttore della classe DbContext personalizzata:

Database.SetInitializer<YourDbContext>(null);

Ad esempio, questa è un'opzione sicuro da utilizzare quando si rilascia gli aggiornamenti ai sistemi esistenti. La disabilitazione di inizializzatori garantisce che perdere i dati esistenti indipendentemente.

Conclusioni

Alla fine della giornata, Code First consente di scrittura di applicazioni multi-tenant e multi-cliente non presenta difficoltà maggiori rispetto alle applicazioni scritte in modo specifico per una configurazione nota e client. Tutto ciò che serve è un po' di informazioni sull'assegnazione di stringhe di connessione e il processo di inizializzazione. In Entity Framework Core, i principi fondamentali rimangono inalterati anche se i dettagli di come funziona in definitiva sono diversi. In particolare, la nuova classe DbContext offre un metodo sottoponibile a override OnConfiguring attraverso il quale il contesto di connettersi al provider di database di scelta e passare le credenziali e qualsiasi altra cosa.


Dino Esposito è l'autore di "Microsoft .NET:  Architettura delle applicazioni per l'azienda"(Microsoft Press, 2014) e"Moderne applicazioni Web con ASP.NET"(Microsoft Press, 2016). Technical evangelist per .NET e a piattaforme Android al JetBrains e spesso come relatore a eventi del settore in tutto il mondo, Esposito condivide la sua visione del software in software2cents@wordpress.com e su Twitter: @despos.

Grazie al seguente esperto tecnico Microsoft per la revisione dell'articolo: Andrea Saltarello (andrea.saltarello@manageddesigns.it)
Andrea Saltarello è un architetto di Milano, Italia, un imprenditore e software che ancora amano la scrittura di codice di progetti per ottenere commenti e suggerimenti sulla sua decisioni di progettazione. Un insegnante e relatore, aveva ha diverse pubbliche pronuncia per corsi e conferenze in Europa, ad esempio TechEd Europe, DevWeek e progettista Software. Egli è stato un MVP Microsoft dal 2003 e recentemente è stato designato un Microsoft Regional Director. È passione per la musica e viene dedicata alla modalità Depeche, con cui è stato appassionato sin "Tutto ciò che conta" in ascolto per la prima volta.