01 May 2011 CodeFirst, EF, HowTo, SQL CE Robert Muehsig

imageWenn man mal "schnell” eine kleine Datenbank braucht - ohne großen SQL Server oder sich erst mit Sqlite und co. rumzuschlagen kann auch auf SQL CE 4.0 zurückgegriffen werden. Zusammen mit dem Entity Framework CodeFirst Modell kann man recht schnell eine kleine Anwendung zusammenbauen. CodeFirst kann natürlich auch in Verbindung mit einem richtigen SQL Server angewandt werden. Hier mal ein kurzes Tutorial, wie man das Initial zum Laufen bekommt.

SQL CE 4.0? Warum nicht Sqlite, eine XML Datei etc.?

Geht natürlich auch - allerdings muss man dann auf das Entity Framework verzichten (ob das schlimm ist, muss jeder für sich entscheiden ;) ). SQL CE 4.0 ist der Mini-Bruder vom SQL Server - demzufolge könnte man auch später ein Upgrade ohne Probleme vollziehen. SQL CE 4.0 läuft mit im Prozess und bedarf keiner weiteren Installation beim Betrieb. Man muss nur die passenden Dlls mit ausliefern und es läuft.

Eine genauere Beschreibung liefert ScottGu: VS 2010 SP1 and SQL CE

EF CodeFirst? Was das?

Das Entity Framework mit seinem gigantischen .EDMX Model ist schon etwas ziemlich gewaltiges und meiner Meinung nach nicht gerade elegant und nützlich. Mit EF CodeFirst wird ein "leichtes” Modell angeboten, in dem man sowohl die Vorzüge des EFs (LINQ!) und das Mappen auf eigene POCOs nutzen kann. (Andere Frameworks (z.B. NHibernate) bieten solch ein Feature bereits seit einiger Zeit an, aber diesen fehlte (?) zum Teil lange ein netter LINQ Support.)

Ganz detailliert natürlich auch bei ScottGu: Code-First Development with Entity Framework 4

Beispielapp - wie sieht der Entwicklungsprozess aus?

Da das erste Setup mit Code-First & SQL CE 4.0 etwas Forschungsarbeit mit sich gebracht hat, zeige ich mal ganz kurz wie man die ersten Schritte unternimmt.

Vermutlich ist es besser, wenn man das VS2010 SP1 installiert hat - bin mir aber jetzt nicht ganz sicher ob es wirklich von Nöten ist.

Wir fangen an und erstellen eine ASP.NET MVC 3 Applikation. Wichtig: Vorher das ASP.NET MVC 3 Tools Update installieren. Damit kommt das Entity Framework Update als NuGet Package gleich mit. Nachprüfen kann man das, indem man die Assembly "EntityFramework” in den Referenzen sucht. Diese wird benötigt! Ansonsten via NuGet z.B. nach Code First suchen :)

image

Prüfen ob SQL 4.0 Dev Tools installiert sind

Um zu schauen ob die Dev Tools installiert sind, kann man ein Blick in das "Add Items” (irgendwo in einem Ordner im Projekt *rechtsklick*) Menü werfen:

Sieht man das SQL Server Compact 4.0 Item ist alles ok. Empfehlung von mir: Legt euch eine SQL Server Compact 4.0 DB an, damit werden alle benötigten Referenzen automatisch ins Projekt eingebunden. Anschließend aber die DB wieder rauslöschen, weil diese uns weiter unten ein paar Probleme bereitet :)

image

Wenn man das Item nicht sieht: Im Web Platform Installer die "VS 2010 SP1 Tools for SQL Server Compact” installieren (Alternativ über diesen Link - allerdings fängt dann sofort der Download an und dummerweise nimmt er bei mir immer die deutsche Version (welche ich aber nicht haben will) - besser Web PI)

image

Nach dem Installieren bekam ich diese Meldung:

image

Nach der Installation muss das VS 2010 neugestartet werden - nun im Projekt eine SQL CE DB anlegen um die benötigten Referenzen zu bekommen.

Nächster Schritt: App_Data Verzeichnis erstellen

image

Dieser Schritt sollte bereits erledigt sein: Wenn ihr die DB angelegt habt, sollte euch VS auch gleich das App_Data Verzeichnis angelegt haben. Wenn nicht: Im App_Data Verzeichnis sollen alle Datenbanken und sonstige "Data” Sachen landen. Allerdings wird der Ordner nicht sofort mit angelegt - daher müssen wir den noch manuell hinzufügen (über *rechtsklick* auf das Projekt *Add* - *Add ASP.NET Folders* - App_Data).

ConnectionString anlegen

Nun hinterlegen wir in der Web.config z.B. folgenden ConnectionString:

  <connectionStrings>
    <add name="DatabaseContext" connectionString="Data Source=|DataDirectory|TestWebsiteModelDatabase.sdf" providerName="System.Data.SqlServerCe.4.0"/>
  </connectionStrings>

Ein paar Anmerkungen:

  • DataDirectory steht für das App_Data Verzeichnis der Anwendung.
  • .SDF ist ein SQL Server Compact File (anders als das .MDF vom großen SQL Server)
  • Der Providername ist auf SQL CE 4.0 eingestellt.
  • Der Name des ConnectionStrings wird später noch eine wichtige Rolle spielen!

Ein Model erstellen - Code-First Style

Wir haben User und Kommentare dazu - nichts wirklich aufregendes. Wichtig hierbei: Keine Attribute von irgendwelchen Entity Framework Sachen - simple POCOs.

    public class User
    {
        public string Name { get; set; }
        public Guid Id { get; set; }
        public List<Comment> Comments { get; set; }
    }

    public class Comment
    {
        public Guid Id { get; set; }
        public User User { get; set; }
        public string Text { get; set; }
    }

Controller mit Scaffolding erzeugen

Nun fügen wir ein MVC Controller dazu (auf den Controller Ordner *Rechtsklick* - *Add Controller*).

Hier geben wir den Controller-Namen an, wählen das Scaffolding Template mit den EF Options und wählen die User Klasse aus. Falls die Klasse dort nicht erscheint: Vorher ein Rebuild ausführen!

Als Data context class muss derselbe Name genommen werden wie der Name des Connection Strings! (in meinem Fall also: DatabaseContext).

"Convention over configuration” ist hier das Motto.

image

In den Advanced Options kann man auch noch eine Masterpage auswählen:

image

Ergebnis:

Es wurde dieser Controller automatisch erstellt:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcEFCodeFirst.Models;

namespace MvcEFCodeFirst.Controllers
{ 
    public class UserController : Controller
    {
        private DatabaseContext db = new DatabaseContext();

        //
        // GET: /User/

        public ViewResult Index()
        {
            return View(db.Users.ToList());
        }

        //
        // GET: /User/Details/5

        public ViewResult Details(Guid id)
        {
            User user = db.Users.Find(id);
            return View(user);
        }

        //
        // GET: /User/Create

        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /User/Create

        [HttpPost]
        public ActionResult Create(User user)
        {
            if (ModelState.IsValid)
            {
                user.Id = Guid.NewGuid();
                db.Users.Add(user);
                db.SaveChanges();
                return RedirectToAction("Index");  
            }

            return View(user);
        }
        
        //
        // GET: /User/Edit/5
 
        public ActionResult Edit(Guid id)
        {
            User user = db.Users.Find(id);
            return View(user);
        }

        //
        // POST: /User/Edit/5

        [HttpPost]
        public ActionResult Edit(User user)
        {
            if (ModelState.IsValid)
            {
                db.Entry(user).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(user);
        }

        //
        // GET: /User/Delete/5
 
        public ActionResult Delete(Guid id)
        {
            User user = db.Users.Find(id);
            return View(user);
        }

        //
        // POST: /User/Delete/5

        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(Guid id)
        {            
            User user = db.Users.Find(id);
            db.Users.Remove(user);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Es wurden diese Views angelegt (voll funktionsfähig) :

image

Es wurde eine DatabaseContext Klasse angelegt:

using System.Data.Entity;

namespace MvcEFCodeFirst.Models
{
    public class DatabaseContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, add the following
        // code to the Application_Start method in your Global.asax file.
        // Note: this will destroy and re-create your database with every model change.
        // 
        // System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<MvcEFCodeFirst.Models.DatabaseContext>());

        public DbSet<User> Users { get; set; }
    }
}

Über diese Klasse passieren (siehe Controller) die Zugriffe und es geschieht das Mapping auf das POCO. Interessant ist noch Zeile 14. Wenn man den Anweisungen folgt und diese Zeile in der Global.asax einfügt, wird bei Schemaänderungen eine neue Datenbank angelegt. Datenmigrationen erfolgen aber nicht!

Für Development-Zwecke aber sehr praktisch.

Achtung: Es darf aber keine Datenbank diesen Namens bereits existieren - weil das Framework sonst damit nicht umgehen kann.

Die erstellte Datenbank sieht von der Struktur so aus:

image

Was ist mit den Comments? Tauchen die auch irgendwo auf?

In unserem Model hatten wir weiter oben eine Verbindung zwischen User und Comment gemacht - in der DB wird diese auch abgebildet, allerdings sind diese in den Templates nicht wiederzusehen. Man kann allerdings sicherlich das Scaffolding anpassen, sodass dies nur eine Frage der verfügbaren Templates ist - passender Google Suchbegriff: MvcScaffolding.

Wie sieht so ein View aus:

Da wir nicht wirklich viele Properties festgelegt haben ("ID” wird auch vom Framework erzeugt und kann daher nicht angepasst werden - auch eine Konvention) sieht es nur so aus:

image

image

usw.

Fazit

Für schnelle Prototypen sehr praktisch. Wie sich das Mapping bei komplexeren Sachverhalten verhält: Keine Ahnung. Durch die LINQ Unterstützung und das Scaffolding macht es trotz mancher Setup-Sachen durchaus Spaß.

Deployment

Wer alle Assemblies sich für ein sicheres Deployment zusammensuchen möchte, dem empfehle ich *Rechtsklick* auf das Projekt zu machen und *Add Deployable Dependencies* auszuwählen (in dem Fall hier: Alles anklicken ;) ) :

image

Dann wird noch ein recht großer Ordner in das Projekt ein gehangen in dem die Abhängigkeiten mit drin sind.

Soviel zum kurzen Einstieg in das Thema :)

[ Download Democode ]


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de