Entity Framework

Code First in ADO.NET Entity Framework 4.1

Rowan Miller

ADO.NET Entity Framework 4.1 wurde im April veröffentlicht und enthält eine Reihe von neuen Funktionen, die auf der vorhandenen Entity Framework 4-Funktionalität aufbauen, die in Microsoft .NET Framework 4 und Visual Studio 2010 veröffentlicht wurde.

Entity Framework 4.1 ist als eigenständiges Installationsprogramm (msdn.microsoft.com/data/ee712906) und als NuGet-Paket „EntityFramework“ verfügbar. Außerdem ist Entity Framework 4.1 bei der Installation von ASP.NET MVC 3.01 enthalten.

Entity Framework 4.1 enthält zwei neue wichtige Funktionen: die DbContext-API und Code First. In diesem Artikel erläutere ich, wie Sie diese zwei Funktionen bei der Anwendungsentwicklung verwenden können. Wir betrachten kurz die ersten Schritte mit Code First und gehen dann ausführlicher auf einige der erweiterten Funktionen ein.

Die DbContext-API ist eine vereinfachte Abstraktion des bestehenden ObjectContext-Typs und einer Reihe von anderen Typen, die in früheren Veröffentlichungen von Entity Framework enthalten waren. Die Oberfläche der DbContext-API ist für allgemeine Aufgaben und Kodiermuster optimiert. Die allgemeine Funktionalität ist auf der Stammebene verfügbar. Während Sie durch die API navigieren, erhalten Sie Zugriff auf die erweiterten Funktionen.

Code First ist ein neues Entwicklungsmuster für Entity Framework, das eine Alternative zu den bestehenden Database First- und Model First-Mustern bietet. Mit Code First können Sie ein Modell mithilfe von CLR-Klassen definieren und diese Klassen anschließend einer vorhandenen Datenbank zuordnen oder zum Generieren eines Datenbankschemas verwenden. Zusätzliche Konfigurationen können über Datenanmerkungen oder eine Fluent-API übermittelt werden.

Erste Schritte

Da es Code First schon eine Weile gibt, beschreibe ich die ersten Schritte nicht im Detail. Wenn Sie nicht mit den Grundlagen vertraut sind, können Sie die exemplarische Vorgehensweise für Code First durchgehen (bit.ly/evXlOc). Abbildung 1 enthält den gesamten Code, den Sie zum ersten Erstellen einer Code First-Anwendung benötigen.

Abbildung 1 Erste Schritte mit Code First

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System;

namespace Blogging
{
  class Program
  {
    static void Main(string[] args)
    {
      Database.SetInitializer<BlogContext>(new BlogInitializer());

      // TODO: Make this program do something!
    }
  }

  public class BlogContext : DbContext
  {
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
      // TODO: Perform any fluent API configuration here!
    }
  }

  public class Blog
  {
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Abstract { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
  }

  public class RssEnabledBlog : Blog
  {
    public string RssFeed { get; set; }
  }

  public class Post
  {
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public byte[] Photo { get; set; }

    public virtual Blog Blog { get; set; }
  }

  public class BlogInitializer : DropCreateDatabaseIfModelChanges<BlogContext>
  {
    protected override void Seed(BlogContext context)
    {
      context.Blogs.Add(new RssEnabledBlog
      {
        Name = "blogs.msdn.com/data",
        RssFeed = "https://blogs.msdn.com/b/data/rss.aspx",
        Posts = new List<Post>
        {
          new Post { Title = "Introducing EF4.1" },
          new Post { Title = "Code First with EF4.1" },
        }
      });

      context.Blogs.Add(new Blog { Name = "romiller.com" });
      context.SaveChanges();
    }
  }
}

Damit es nicht zu komplex wird, lasse ich Code First eine Datenbank generieren. Die Datenbank wird erstellt, sobald ich BlogContext erstmalig verwende, um Daten zu speichern und abzufragen. Der folgende Inhalt dieses Artikels gilt gleichermaßen für Fälle, in denen Code First einem vorhandenen Datenbankschema zugeordnet wird. Ich verwende einen Datenbankinitialisierer, um die Datenbank zu löschen und neu zu erstellen, wenn das Modell im Laufe des Artikels geändert wird.

Zuordnen mit der Fluent-API

Code First beginnt mit der Überprüfung der CLR-Klassen, um die Form des Modells abzuleiten. Zur Erkennung von Primärschlüsseln und anderen Eigenschaften dient eine Reihe von Konventionen. Sie können mithilfe von Datenanmerkungen oder einer Fluent-API überschreiben oder ergänzen, was durch die Konventionen erkannt wurde. Über die Realisation allgemeiner Aufgaben mithilfe der Fluent-API gibt es eine Reihe von Artikeln, daher erläutere ich einige Aspekte der erweiterten Konfiguration, die ausgeführt werden kann. Dabei gehe ich insbesondere auf die Zuordnungsabschnitte der API ein. Eine Zuordnungskonfiguration kann für die Zuordnung zu einem vorhandenen Datenbankschema verwendet werden oder Auswirkungen auf die Form eines generierten Schemas haben. Die Fluent-API wird über den DbModelBuilder-Typ verfügbar gemacht. Der Zugriff auf die Fluent-API erfolgt am einfachsten durch das Überschreiben der OnModelCreating-Methode auf DbContext.

Entitätsaufteilung Durch die Aufteilung von Entitäten können die Eigenschaften eines Entitätstyps über mehrere Tabellen verteilt werden. Ein Beispiel: Ich möchte die Fotodaten für Beiträge trennen und in einer separaten Tabelle platzieren, damit sie in einer anderen Dateigruppe gespeichert werden können. Die Entitätsaufteilung verwendet mehrere Map-Aufrufe, um eine Teilmenge von Eigenschaften zu einer bestimmten Tabelle zuzuordnen. In Abbildung 2 ordne ich die Photo-Eigenschaft der Tabelle „PostPhotos“ und die verbleibenden Eigenschaften der Tabelle „Posts“ zu. Sie sehen, dass ich den Primärschlüssel nicht in die Liste mit Eigenschaften aufgenommen habe. Der Primärschlüssel ist immer in jeder Tabelle erforderlich. Ich hätte ihn berücksichtigen können, aber Code First fügt ihn automatisch hinzu.

Abbildung 2 Entitätsaufteilung

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Post>()
    .Map(m =>
      {
        m.Properties(p => new { p.Title, p.Content });
        m.ToTable("Posts");
      })
    .Map(m =>
      {
        m.Properties(p => new { p.Photo });
        m.ToTable("PostPhotos");
      });
}

Tabelle pro Hierarchie (TPH)-Vererbung Bei der TPH werden die Daten für eine Vererbungshierarchie in einer einzelnen Tabelle gespeichert. Zum Bestimmen des Typs der Zeilen wird eine Diskriminatorspalte verwendet. Code First verwendet standardmäßig TPH, wenn keine Konfiguration bereitgestellt wird. Die Diskriminatorspalte wird treffend „Discriminator“ genannt, und als Diskriminatorwerte werden die CLR-Typnamen jedes Typs verwendet.

Sie können allerdings die Ausführung der TPH-Zuordnung anpassen. Hierzu nutzen Sie die Map-Methode, um die Diskriminatorspaltenwerte für den Basistyp zu konfigurieren. Anschließend konfigurieren Sie mithilfe von Map<Tabellenentitätstyp> alle abgeleiteten Typen. Ich verwende hier die Spalte „HasRssFeed“, um einen TRUE/FALSE-Wert zur Unterscheidung zwischen Blog- und RssEnabledBlog-Instanzen zu speichern:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m => m.Requires("HasRssFeed").HasValue(false))
    .Map<RssEnabledBlog>(m => m.Requires("HasRssFeed").HasValue(true));
}

Im Beispiel oben nutze ich immer noch eine eigene Spalte zur Unterscheidung zwischen Typen, aber RssEnabledBlogs können dadurch identifiziert werden, dass sie einen RSS-Feed haben. Ich kann die Zuordnung also neu schreiben, damit Entity Framework die Spalte, in der Blog.RssFeed gespeichert wird, zur Unterscheidung zwischen Typen verwendet. Wenn die Spalte einen Wert ungleich Null enthält, handelt es sich um einen RssEnabledBlog:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map<RssEnabledBlog>(m => m.Requires(b => b.RssFeed).HasValue());
}

Tabelle pro Typ (TPT)-Vererbung Bei der TPT werden alle Eigenschaften des Basistyps in einer einzelnen Tabelle gespeichert. Alle zusätzlichen Eigenschaften für abgeleitete Typen werden anschließend mit einem Fremdschlüssel zurück zur Basistabelle in separaten Tabellen gespeichert. Die TPT-Zuordnung gibt durch einen Map-Aufruf den Basistabellennamen an. Anschließend dient Map<Tabellenentitätstyp> dazu, die Tabelle für jeden abgeleiteten Typ zu konfigurieren. Im folgenden Beispiel speichere ich Daten, die alle Blogs in der Tabelle „Blogs“ gemeinsam haben, und Daten, die nur die RSS-fähigen Blogs in der Tabelle „RssBlogs“ betreffen:

modelBuilder.Entity<Blog>()
  .Map(m => m.ToTable("Blogs"))
  .Map<RssEnabledBlog>(m => m.ToTable("RssBlogs"));

Tabelle pro konkretem Typ (TPC)-Vererbung Bei der TPC werden die Daten für jeden Typ in einer vollständig separaten Tabelle ohne Fremdschlüsseleinschränkungen zwischen den Tabellen gespeichert. Die Konfiguration ist wie bei der TPT-Zuordnung, mit der Ausnahme, dass Sie einen MapInheritedProperties-Aufruf beim Konfigurieren jedes einzelnen abgeleiteten Typs hinzufügen. MapInheritedProperties weist Code First an, alle von der Basisklasse vererbten Eigenschaften zu neuen Spalten in der Tabelle für die abgeleitete Klasse zuzuordnen:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m => m.ToTable("Blogs"))
    .Map<RssEnabledBlog>(m =>
      {
        m.MapInheritedProperties();
        m.ToTable("RssBlogs");
      });
}

Der Konvention entsprechend verwendet Code First Identitätsspalten für ganzzahlige Primärschlüssel. Mit TPC existiert aber nicht mehr eine einzelne Tabelle, die alle Blogs enthält, die zum Generieren von Primärschlüsseln verwendet werden können. Code First schaltet daher die Identität ab, wenn Sie die TPC-Zuordnung verwenden. Wenn Sie eine Zuordnung zu einer vorhandenen Datenbank vornehmen, die zur Generierung eindeutiger Werte über mehrere Tabellen hinweg eingerichtet wurde, können Sie die Identität über den Eigenschaftenkonfigurationsabschnitt der Fluent-API erneut aktivieren.

Hybride Zuordnungen Die Form des Schemas entspricht natürlich nicht immer einem der erläuterten Muster, insbesondere im Falle der Zuordnung zu einer bestehenden Datenbank. Das Gute dabei ist, dass die Zuordnungs-API zusammensetzbar ist. Sie können mehrere Zuordnungsstrategien kombinieren. Abbildung 3 enthält ein Beispiel, in dem die Entitätenaufteilung mit der TPT-Vererbungszuordnung kombiniert wird. Die Daten für Blogs werden zwischen den Tabellen „Blogs“ und „BlogAbstracts“ aufgeteilt. Die Daten, die nur RSS-fähige Blogs betreffen, werden in der separaten Tabelle „RssBlogs“ gespeichert.

Abbildung 3 Kombinieren der Entitätenaufteilung mit der TPT-Vererbungszuordnung

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m =>
      {
        m.Properties(b => new { b.Name });
        m.ToTable("Blogs");
      })
    .Map(m =>
      {
        m.Properties(b => new { b.Abstract });
        m.ToTable("BlogAbstracts");
      })
    .Map<RssEnabledBlog>(m =>
      {
         m.ToTable("RssBlogs");
      });
}

Änderungsprotokollierungs-API

Nach dem Konfigurieren von Datenbankzuordnungen möchte ich jetzt die Arbeit mit Daten erläutern. Ich gehe sofort ausführlich auf einige erweiterte Szenarios ein. Wenn Sie nicht mit den Grundlagen des Datenzugriffs vertraut sind, lesen Sie kurz die oben erwähnte exemplarische Vorgehensweise für Code First.

Statusinformationen für eine einzelne Entität In vielen Fällen, beispielsweise bei der Protokollierung, ist es sinnvoll, Zugriff auf die Statusinformation für eine Entität zu erhalten. Dazu kann beispielsweise der Status der Entität oder die Information darüber zählen, welche Eigenschaften geändert wurden. DbContext bietet über die Entry-Methode den Zugriff auf diese Informationen für eine einzelne Entität. Der Codeausschnitt in Abbildung 4 lädt einen „Blog“ aus der Datenbank, ändert eine Eigenschaft und gibt anschließend die aktuellen Werte und die Originalwerte für jede Eigenschaft an die Konsole aus.

Abbildung 4 Abrufen von Statusinformationen für eine Entität

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Change the name of one blog
    var blog = db.Blogs.First();
    blog.Name = "ADO.NET Team Blog";

    // Print out original and current value for each property
    var propertyNames = db.Entry(blog).CurrentValues.PropertyNames;
    foreach (var property in propertyNames)
    {
      System.Console.WriteLine(
        "{0}\n Original Value: {1}\n Current Value: {2}", 
        property, 
        db.Entry(blog).OriginalValues[property],
        db.Entry(blog).CurrentValues[property]);
    }
  }

  Console.ReadKey();
}

Die Ausführung des Codes in Abbildung 4 erzeugt folgende Konsolenausgabe:

BlogId
 Original Value: 1
 Current Value: 1
 
Name
 Original Value: blogs.msdn.com/data
 Current Value: ADO.NET Team Blog
 
Abstract
 Original Value:
 Current Value:
 
RssFeed
 Original Value: https://blogs.msdn.com/b/data/rss.aspx
 Current Value: https://blogs.msdn.com/b/data/rss.aspx

Statusinformationen für mehrere Entitäten Mit DbContext können Sie über die ChangeTracker.Entries-Methode auf Informationen für mehrere Entitäten zugreifen. Es gibt sowohl eine generische Überladung, die Entitäten eines bestimmten Typs bereitstellt, als auch eine nicht generische Überladung, die alle Entitäten bereitstellt. Der generische Parameter muss kein Entitätstyp sein. Sie können zum Beispiel Einträge für alle geladenen Objekte erhalten, die eine bestimmte Schnittstelle implementieren. Der Code in Abbildung 5 zeigt das Laden aller Blogs in den Speicher, die Änderung einer Eigenschaft für einen der Blogs und die anschließende Ausgabe des Status für jeden protokollierten Blog.

Abbildung 5 Zugriff auf Informationen für mehrere Entitäten mit DbContext

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load all blogs into memory
    db.Blogs.Load();

    // Change the name of one blog
    var blog = db.Blogs.First();
    blog.Name = "ADO.NET Team Blog";

    // Print out state for each blog that is in memory
    foreach (var entry in db.ChangeTracker.Entries<Blog>())
    {
      Console.WriteLine("BlogId: {0}\n State: {1}\n",
        entry.Entity.BlogId,
        entry.State);
    }
  }

Die Ausführung des Codes in Abbildung 5 erzeugt folgende Konsolenausgabe:

BlogId: 1
  State: Modified
 
BlogId: 2
  State: Unchanged

Abfragen lokaler Instanzen Bei jedem Ausführen einer LINQ-Abfrage für ein DbSet wird die Abfrage zur Verarbeitung an die Datenbank gesendet. Dies gewährleistet, dass Sie immer vollständige und aktuelle Ergebnisse erhalten. Wenn Sie allerdings wissen, dass alle von Ihnen benötigten Daten bereits im Speicher sind, können Sie einen Roundtrip zur Datenbank vermeiden, indem Sie die lokalen Daten abfragen. Der Code in Abbildung 6 lädt alle Blogs in den Speicher und führt anschließend zwei LINQ-Abfragen für Blogs aus, die nicht an die Datenbank gesendet werden.

Abbildung 6 Ausführen von LINQ-Abfragen für Daten im Speicher

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load all blogs into memory
    db.Blogs.Load();

    // Query for blogs ordered by name
    var orderedBlogs = from b in db.Blogs.Local 
                       orderby b.Name
                       select b;

    Console.WriteLine("All Blogs:");
    foreach (var blog in orderedBlogs)
    {
      Console.WriteLine(" - {0}", blog.Name);
    }

    // Query for all RSS enabled blogs
    var rssBlogs = from b in db.Blogs.Local
                   where b is RssEnabledBlog
                   select b;

    Console.WriteLine("\n Rss Blog Count: {0}", rssBlogs.Count());
  }

  Console.ReadKey();
}

Die Ausführung des Codes in Abbildung 6 erzeugt folgende Konsolenausgabe:

All Blogs:
 - blogs.msdn.com/data
 - romiller.com
 
Rss Blog Count: 1

Navigationseigenschaft als Abfrage Mit DbContext können Sie eine Abfrage erhalten, die die Inhalte einer Navigationseigenschaft für eine gegebene Entitätsinstanz darstellt. Dadurch können Sie die Elemente, die Sie im Speicher bereitstellen möchten, formen oder filtern und zudem vermeiden, nicht erforderliche Daten zu erhalten.

Als Beispiel: Ich habe eine Instanz eines Blogs und möchte wissen, wie viele Beiträge darin enthalten sind. Ich kann den Code schreiben, der in Abbildung 7 dargestellt ist. Dieser Code nutzt aber Lazy Loading, um alle fraglichen Beiträge zurück in den Speicher zu holen, nur damit ich die Anzahl ermitteln kann.

Abbildung 7 Ermitteln einer Anzahl von Datenbankelementen mit Lazy Loading

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load a single blog
    var blog = db.Blogs.First();

    // Print out the number of posts
    Console.WriteLine("Blog {0} has {1} posts.",
      blog.BlogId,
      blog.Posts.Count());
  }

  Console.ReadKey();
}

Im Vergleich zu dem von mir benötigten einzelnen Ganzzahlergebnis werden sehr viele Daten von der Datenbank übertragen und belegen Speicherplatz.

Zum Glück kann ich den Code optimieren. Ich verwende die Entry-Methode für DbContext, um eine Abfrage zu erhalten, die die Auflistung von mit dem Blog verbundenen Beiträgen darstellt. Da LINQ zusammensetzbar ist, kann ich den Count-Operator verketten, und die gesamte Abfrage wird an die Datenbank gesendet, sodass nur das einzelne Ganzzahlergebnis zurückgegeben wird. Siehe dazu Abbildung 8.

Abbildung 8 Optimieren von Abfragecode und Sparen von Ressourcen mit DbContext

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load a single blog
    var blog = db.Blogs.First();

    // Query for count
    var postCount = db.Entry(blog)
      .Collection(b => b.Posts)
      .Query()
      .Count();

    // Print out the number of posts
    Console.WriteLine("Blog {0} has {1} posts.",
      blog.BlogId,
      postCount);
  }

  Console.ReadKey();
}

Aspekte beim Bereitstellen

Bisher habe ich erläutert, wie der Zugriff auf Daten funktioniert. Lassen Sie uns jetzt einen Blick auf einige Aspekte werfen, die Sie berücksichtigen müssen, wenn Sie die Anwendung weiterentwickeln und sich einer Produktionsversion nähern.

Verbindungszeichenfolgen Bisher habe ich nur Code First eine Datenbank auf „localhost\SQLEXPRESS“ generieren lassen. Wenn es Zeit für die Bereitstellung der Anwendung ist, möchte ich wahrscheinlich die Datenbank ändern, auf die Code First zunächst zeigt. Der empfohlene Ansatz dazu besteht darin, der Datei „App.config“ bzw. bei Webanwendungen „Web.config“ einen Eintrag mit einer Verbindungszeichenfolge hinzuzufügen. Diese Vorgehensweise wird auch empfohlen, um Code First für die Zuordnung zu einer vorhandenen Datenbank zu verwenden. Wenn der Name der Verbindungszeichenfolge mit dem vollständig qualifizierten Typnamen des Kontexts übereinstimmt, übernimmt Code First ihn zur Laufzeit automatisch. Es wird jedoch empfohlen, den DbContext-Konstruktor zu verwenden, der einen Verbindungsnamen mit der Syntax „name=<Name der Verbindungszeichenfolge>“ akzeptiert. Dadurch wird gewährleistet, dass Code First immer die CONFIG-Datei verwendet. Wenn der Eintrag mit der Verbindungszeichenfolge nicht gefunden wird, löst dies eine Ausnahme aus. Das folgende Beispiel zeigt den Abschnitt mit der Verbindungszeichenfolge, der die Datenbank beeinflussen kann, die das Ziel der Beispielanwendung ist:

<connectionStrings>
  <add 
    name="Blogging" 
    providerName="System.Data.SqlClient"
    connectionString="Server=MyServer;Database=Blogging;
    Integrated Security=True;MultipleActiveResultSets=True;" />
</connectionStrings>

Hier sehen Sie den aktualisierten Kontextcode:

public class BlogContext : DbContext
{
  public BlogContext() 
    : base("name=Blogging")
  {}

  public DbSet<Blog> Blogs { get; set; }
  public DbSet<Post> Posts { get; set; }
}

Es wird empfohlen, „Multiple Active Result Sets“ zu aktivieren. Dadurch können zwei Abfragen zur selben Zeit aktiv sein. Das kann beispielsweise erforderlich sein, um Beiträge abzufragen, die mit einem Blog verbunden sind, während alle Blogs aufgelistet werden.

Datenbankinitialisierer Code First erstellt standardmäßig automatisch eine Datenbank, wenn die Zieldatenbank nicht existiert. Für einige Entwickler ist dies auch bei der Bereitstellung die gewünschte Funktionalität, und nur die Produktionsdatenbank wird beim ersten Starten der Anwendung erstellt. Wenn die Produktionsumgebung eine Aufgabe von Datenbankadministratoren ist, erstellen diese höchstwahrscheinlich die Produktionsdatenbank für Sie, und nach der Bereitstellung der Anwendung schlägt diese fehl, wenn die Zieldatenbank nicht existiert. Ich habe in diesem Artikel auch die Standardinitialisierungslogik überschrieben und die Datenbank so konfiguriert, dass sie bei jeder Änderung des Schemas gelöscht und neu erstellt wird. Dies muss unbedingt geändert werden, sobald Sie die Anwendung für die Produktion bereitstellen.

Der empfohlene Ansatz zum Ändern oder Deaktivieren von Initialisierungsverhalten bei der Bereitstellung ist, die Datei „App.config“ bzw. bei Webanwendungen „Web.config“ zu verwenden. Fügen Sie im appSettings-Abschnitt einen Eintrag mit dem Schlüssel „DatabaseInitializerForType“ hinzu, gefolgt vom Kontexttypnamen und der Assembly, in der er definiert wird. Der Wert kann entweder „Disabled“ sein oder auf den Initialisierertypnamen lauten, gefolgt von der Assembly, in der er definiert wird.

Im folgenden Beispiel wird jede von mir in diesem Artikel verwendete Initialisierungslogik für den Kontext deaktiviert:

<appSettings>
  <add 
    key="DatabaseInitializerForType Blogging.BlogContext, Blogging" 
    value="Disabled" />
</appSettings>

Im nächsten Beispiel wird der Initialisierer wieder in die Standardfunktionalität geändert, mit der die Datenbank nur erstellt wird, wenn sie nicht existiert:

<appSettings>
  <add 
    key="DatabaseInitializerForType Blogging.BlogContext, Blogging" 
    value="System.Data.Entity.CreateDatabaseIfNotExists EntityFramework" />
</appSettings>

Benutzerkonten Wenn Sie entscheiden, dass die Produktionsanwendung die Datenbank erstellen soll, muss die Anwendung anfänglich unter Verwendung eines Kontos ausgeführt werden, das über Berechtigungen zum Erstellen der Datenbank und Ändern des Schemas verfügt. Wenn diese Berechtigungen bestehen bleiben, bedeutet das ein deutlich erhöhtes Sicherheitsrisiko für die Anwendung. Ich rate dringend dazu, Anwendungen mit dem kleinsten Satz an Berechtigungen, die zum Abfragen und Speichern von Daten erforderlich sind, auszuführen.

Weiterführende Informationen

Ich habe in diesem Artikel einen kurzen Blick auf die ersten Schritte bei der Entwicklung mit Code First und die neue DbContext-API geworfen, die beide in ADO.NET Entity Framework 4.1 enthalten sind. Sie haben gesehen, wie die Fluent-API verwendet werden kann, um eine vorhandene Datenbank zuzuordnen oder die Form eines von Code First generierten Datenbankschemas zu beeinflussen. Danach habe ich die Änderungsprotokollierungs-API und ihre Verwendung zum Abfragen lokaler Entitätsinstanzen und zusätzlicher Informationen zu diesen Instanzen erläutert. Zum Schluss bin ich auf einige Aspekte bei der Bereitstellung einer Anwendung, die Code First für den Datenzugriff verwendet, eingegangen.

Besuchen Sie msdn.com/data/ef, um mehr über die Funktionen zu erfahren, die Entity Framework 4.1 bietet. Hilfe bei der Verwendung von Entity Framework 4.1 erhalten Sie außerdem im Forum des Datenentwicklercenters: bit.ly/166o1Z.

Rowan Miller ist Programmmanager im Entity Framework-Team von Microsoft. Weitere Informationen über Entity Framework erhalten Sie in seinem Blog unter romiller.com.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Arthur Vickers