Archiv der Kategorie: Entity Framework

Entity Framework 6 provider wechsel–MSSQL und PostGreSql 2.1.1.0


Vor einiger Zeit habe ich erläutert wie man das gleiche EF Model für eine MSSQL Datenbank und MySQL Datenbank verwenden kann. Das “gleiche” geht natürlich auch mit anderen SQL Datenbanken wie z.B. PostGreSql. Nur das die Verwendung für PostGreSql mit einigen Tücken auf sich aufmerksam machte bei der Umsetzung. Wie man eine normale PostGreSql Datenbank mit .NET EF anspricht habe ich bereits in meinem Artikel “Postgres Datenbank und Entity Framework mit npgsql” beschrieben. Im Aktuellen Artikel werde ich auf die Grundlagen der beiden bereits geschriebenen Artikel verweisen und hier nur noch auf die entstandenen Probleme eingehen, denn das Vorgehen ist hier durchaus ähnlich.

Wie bereits bekannt, dient als Grundlage eine MSSQL Datenbank die bereits in einem EDMX abgebildet ist. Wir müssen dann die passende SSDL für die zugehörige PostGreSql Datenbank ergänzen. Als erstes müssen wir also unsere Tabellen aus der MSSQL Datenbank auch in der PostGres Datenbank anlegen und hier aufpassen, das auch nur die kompatiblen Datentypen verwendet werden die beide SQL Systeme und .NET verstehen.

Ich habe bei der Datenbankkonvertierung auf die Trial Version von MS SQL to PostgreSQL gesetzt, da ich keine Datensätze zu migrieren hatte und die Fremdschlüsselbeziehungen per “Hand” nachgetragen habe, denn die Trial Version hat hier einige Einschränkungen. Außerdem wurde beim Konvertieren kein Autoinkrement konvertiert, was ebenfalls angepasst werden musste mit Hilfe des Datentyps “serial” and “bigserial” in PostGreSql.

Nachdem man zwei “gleiche” Datenbanken hat für MSSQL und PostGres kann man mit Hilfe des Tools “edmGen” die passenden Daten für die PostGres Datenbank erstellen, wozu auch unsere SSDL gehört, die wir für den Providerswitch zwischen MSSQL und PostGreSql benötigen. Damit edmGen ausgeführt werden kann müssen die entsprechenden npgsql Providerdaten im GAC registriert werden (die Dateien Selbst kann man sich inzwischen auf über NuGet ziehen). Für EF6 wird hier aber zusätzlich noch die “Npgsql.EntityFrameworkLegacy.dll” und die “Npgsql.EntityFramework.dll” im GAC benötigt, die zusätzlich zur “Npgsql.dll” registriert werden müssen, wie bereits in einem meiner älteren Artikel erläutert. Da es evtl. zu Kompatibilitätsproblemen führen kann zwischen EF6 und der “Npgsql.EntityFrameworkLegacy.dll”, da diese eigentlich für alle EF Version Kleiner 6.0 gedacht ist, habe ich mir hier ein Beispielprojekt angelegt in dem ich per NuGet die aktuelle Version der Legacy Variante kopiere und dann diese Datei im Hauptprojekt direkt verweise in dem ich EF6 verwende. Denn die Legacy Datei wird nur für das Erstellen der Modeldaten mit edmGen verwendet. Nachdem man mit edmGen die passenden Modeldaten für unsere PostGres Datenbank erstellt hat benötigen wir hier nur die SSDL. Die SSDL einfach im passenden Projekt wo z.B. unser EDMX ist ablegen und als Resource einbetten. Wie der passende Connectionstring auszusehen hat, habe ich bereits in einem meiner anderen Artikel beschrieben.

Die SSDL muss aber noch “angepasst” werden, denn aktuell würde hier die Fehlermeldung:

„Schema specified is not valid. Errors: error 2102: The version of EdmItemCollection must match the version of StoreItemCollection.“

kommen, ich denke es ist noch ein Bug in den aktuellen npgsql 2.1.1.0 Version für EF6. Damit dieser Fehler nicht auftritt, muss in der SSDL für PostGres der gleiche xmlns Namespace eingetragen sein wie in der SSDL für MSSQL. Wenn dieser aktuell verglichen wird:

MSSQL im Schema Tag der SSDL: xmlns=“http://schemas.microsoft.com/ado/2009/11/edm/ssdl”

PostGreSQL im Schema Tag der SSDL: xmlns=“http://schemas.microsoft.com/ado/2009/02/edm/ssdl“

Hier muss zwingend der Wert von “/02/” auf “/11/” geändert werden, d.h. in der PostGres SSDL muss der gleiche xmlns Eintrag stehen wie in der MSSQL SSDL.

Jetzt muss nur noch die App.Config entsprechend angepasst werden auf npgsql und Entity Framework 6 und schon funktioniert das ganze wie bei MySql.

Hinweis: Ich hatte bei der Konvertierung der Datenbank noch Probleme mit einigen Datentypen die nicht “richtig” konvertiert wurden, dann erhält man z.B: die folgende Fehlermeldung:

testModel.msl(343,12) : error 2019: Member Mapping specified is not valid. The type ‚Edm.Double[Nullable=False,DefaultValue=]‘ of member ‚costs‘ in type ‚test.cost‘ is not compatible with ‚Npgsql.float4[Nullable=False,DefaultValue=]‘ of member costs‘ in type ‚test.Store.cost‘.

Hier muss nach einem passenden Datentyp gesucht werden der in beiden SQL Versionen “übereinstimmt”.

Entity Framwork 6 provider wechsel – MSSQL und MySQL


Oft muss eine Software sehr flexibel gestaltet werden, damit diese so vielen Kundenwünschen wie möglich entspricht. Hier muss z.B. auch die DB Schnittstelle eine entsprechende Flexibilität aufweisen, damit man nicht nur MS SQL Server unterstützt sondern z.B. auch MySQL.

Wenn man also eine Software erstellen möchte die auf unterschiedlichen Datenbanken laufen soll, sucht man sich am Besten ein passendes Framework wie das Entity Framework. Jetzt muss man noch darauf achten, das man in seinen Modeldaten nur Datentypen verwendet, die sowohl MS SQL und MySQL unterstützten. MySQL unterstützt z.B. kein “varchar(max)”, hier muss dann entsprechend abgewogen werden welcher Datentyp bzw. welche Länge wirklich benötigt wird.

Der Providerwechsel bzw. die Umsetzung, kann mit dem Entity Framework sehr einfach durchgeführt werden. Hier muss aber darauf hingewiesen werden, das der Release von EF6 erst vor kurzem war und es z.B. mit Visual Studio 2013 zu diversen Problemen kommen kann, wenn man ein passendes MySQL Script generieren möchte.

1. Installieren der Voraussetzungen

Als erstes benötigen wir einen aktuellen Connector für MySQL in der Version 6.8.1.0 oder höher, zu finden unter: http://dev.mysql.com/downloads/connector/net/

2. Erstellen einer Beispiel Konsolenanwendung

Legen Sie eine Konsolenanwendung für .NET 4 an und fügen Sie der die folgenden Verweise hinzu:

  • MySql.Data.dll (Version 6.8.1.0 oder höher)
  • MySql.Data.Entity.EF6.dll (Achtung aufpassen für .NET 4.0 und nicht 4.5 – für unser Beispiel)
  • EntityFramework.dll (Version 6+)
  • EntityFramework.SqlServer
  • System.Data.Entity
  • System.Configuration

Die MySql DLLs wurden vom Connector für MySql installiert und sollte man unter den Standard Assemblies finden.

3. Anlegen der passenden Datenbanken in MySQL und MS SQL

Der einfachste Weg um zwei “identische” Datenbanken für MySQL und MS SQL zu bekommen, ist es ein Model zu erstellen und dann entsprechend die SQL Skripte vom Visual Studio erstellen zu lassen.

Das Erstellen eines MySQL Skriptes über das Visual Studio ist aktuell aber noch sehr “fehleranfällig” und ist mir nur in einer bestimmten Konstellation gelungen. Dazu habe ich auf einer Virtuellen Maschine nur Visual Studio 2012 und dotConnect für MySQL installiert. Dann habe ich das entsprechende Datenmodell erstellt und darauf geachtet, das ich nur Datentypen verwende die auch von beiden DB Systemen unterstützt werden. Da ich Visual Studio 2012 verwendet habe, handelte es sich hier auch um ein EF5 Model! – dies sollte aber nur zum erstellen der SQL Skripte verwendet werden.

Wenn man dann in den Eigenschaften der EDMX Datei die DDL Genration Template auf “Devart SSDLToMySql.tt” stellt und dann im Model auf “Generate Database from Model” klickt wird einem das passende MySQL Script erstellt.image

Mit dem Originaltemplate “SSDLToSQL10.tt” erhält man natürlich das passende MS SQL Script.

Ich denke wenn die einzelnen Anbieter noch etwas Zeit bekommen, ist es bestimmt bald möglich alles direkt mit EF6 in V2013 auszuführen. Dies war mir aber mit den aktuellen Treiberversionenversionen nicht möglich.

4. Passende MySql SSDL extrahieren

Damit wir später auch entsprechende Abfragen auf der MySql Datenbank ausführen können, benötigen wir noch die passende SSDL von der MySql Datenbank. Denn wenn wir später zwischen MySql und MS Sql Datenbank wechseln wollen, benötigen wir eine SSDL für die MySql Datenbank und eine SSDL für die MS SQL Datenbank. In meinem Beispiel extrahieren wir die SSDL für die MySql Datenbank und verwenden die Standard SSDL aus dem EDMX Model für die MS SQL Datenbank.

Die SSDL ist in der EDMX Model Datei “verpackt”. Als erstes benötigen wir eine EDMX Datei bei der wir das Model aus der MySql Datenbank erstellt haben. Dann öffnet man die EDMX Datei mit einem einfachen SQL Editor über das Visual Studio

image

Und wir extrahieren den Abschnitt nach “<!– SSDL content –>” von “<Schema …>” bis zum “</Schema>” Ende und erstellen eine neue Datei z.B. EFModel.MySql.ssdl, hier fügen wir als erste Zeile folgendes ein

<?xml version="1.0" encoding="utf-8"?>

Der Rest wird mit unseren XML Daten aus dem EDMX gefüllt, das ganze könnte dann z.B. folgendermaßen aussehen

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="testDbModel.Store" 
        Alias="Self" 
        Provider="MySql.Data.MySqlClient" 
        ProviderManifestToken="5.6" 
        xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" 
        xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
  <EntityContainer Name="testDbModelStoreContainer">
    <EntitySet Name="person" EntityType="testDbModel.Store.person" store:Type="Tables" Schema="testdb" />
  </EntityContainer>
  <EntityType Name="person">
    <Key>
      <PropertyRef Name="Name" />
    </Key>
    <Property Name="Name" Type="char" Nullable="false" MaxLength="10" />
    <Property Name="Vorname" Type="char" MaxLength="10" />
    <Property Name="Age" Type="int" />
    <Property Name="Geburtsdatum" Type="datetime" />
    <Property Name="IsMale" Type="bit" />
  </EntityType>
</Schema>

Hier ist wichtig, das als Provider “MySql.Data.MySqlClient” eingetragen ist. Sollte dies nicht der Fall sein, dann wurde das Model aus der falschen DB erstellt. Die Datei legen wir mit im EF Projekt im Selben Pfad wie die EDMX ab und stellen unter den Eigenschaften der Datei ein, das diese mit ins Ausgabeverzeichnis kopiert werden soll.

image

Auf die SSDL selbst greifen wir erst wieder im Connectionstring zu.

5. Anpassen der App.config (Connectionstring erstellen und Provider hinzufügen)

Als nächstes müssen wir die App.config anpassen, denn für EF6 gibt es hier weitere Einstellungen die vorgenommen werden müssen. Dazu gehört es die passenden Provider für MS SQL und MySQL in der App.config einzutragen, die Angabe des Providers wird ab EF6 benötigt.

<entityFramework>
  <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, mysql.data.entity.EF6" />
  </providers>
</entityFramework>

Auch die jeweils passenden Connectionstrings für die MySQL und MS SQL Datenbank fügen wir der App.config hinzu. Hier ist der entsprechende “provider” angepasst, der zugehörige “provider connection string” und die SSDL für MySql angepasst.

  <connectionStrings>
    <add name="MsSQLConn" connectionString="metadata=res://*/EFModel.csdl|res://*/EFModel.ssdl|res://*/EFModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=172.168.0.1\SQL2008R2;initial catalog=testDb;persist security info=True;user id=sa;password=meinPasswort;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
    <add name="MySQLConn" connectionString="metadata=EFModel.MySql.ssdl|res://*/EFModel.csdl|res://*/EFModel.msl;provider=MySql.Data.MySqlClient;provider connection string=&quot;server=localhost;user id=root;password=meinPasswort;persistsecurityinfo=True;database=testDb&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>

Im Connectionstring für MySql muss die passende SSDL für “EFModel.MySql.ssdl” eingetragen werden, hier ist es wichtig das diese ohne “res:/*//…” eingetragen wird. Die Datei wird nur vom EF gefunden, wenn diese auch auf “Copy to Output Directory” gestellt wurde, denn sonst kommt es beim Ausführen von MySql Anweisungen z.B. zu folgendem Fehler:

“System.Data.MetadataException: The specified metadata path is not valid. A valid path must be either an existing directory, an existing file with extension ‚.csdl‘, ‚.ssdl‘, or ‚.msl‘, or a URI that identifies an embedded resource. …”

Wenn man keine extra SSDL z.B. für MySql einbindet, dann kommt es bei SQL Abfragen zu folgendem Fehler:

„Unable to cast object of type ‚MySql.Data.MySqlClient.MySqlConnection‘ to type ‚System.Data.SqlClient.SqlConnection‘.“

UPDATE: Es ist auch möglich die MySql.ssdl Datei mit als Resource in die DDL einzubinden. Wichtig ist, dass dann unter Metadata der Namespace in dem sich die ssdl befindet mit vor den Namen der ssdl geschrieben wird. D.h. wenn die DLL den Namespace “efdata” hat und die ssdl den Namen “EFModel.MySql.ssdl” dann sieht der Metadata Eintrag für die MySql.ssdl folgendermaßen aus: “res://*/efdata.EFModel.MySql.ssdl|res://*/edoModel.csdl…”. Sollte dies nicht funktionieren, dann muss die DDL in der die ssdl als Resource eingebettet wurde mit z.B. ILSpy angeschaut werden und dort sieht man dann auch den genauen Namen der eingebetteten ssdl Resource.

6. Verwenden der Connectionstrings

Ich habe mein EF Model entsprechend erweitert, das ich den Connectionstring direkt übergeben kann. Daher kann ich den passenden Connectionstring direkt aus der App.config verwenden um z.B: eine neue Person der MS SQL Datenbank hinzuzufügen.

string connectionString = ConfigurationManager.ConnectionStrings["MsSQLConn"].ConnectionString;
testDbEntities entities = new testDbEntities(connectionString);
entities.Person.Add(new Person() { Age = 23, Geburtsdatum = DateTime.Now, IsMale = true, Name = "Test", Vorname = "Blubb"});
entities.SaveChanges();

Das einzige was angepasst werden muss, wenn man die Daten der MySQL Datenbank hinzufügen möchte ist, das “MsSQLConn” in ein “MySQLConn” zu ändern, um den MySQL Connectionstring zu ermitteln.

SimpleMembership mit ASP.NET MVC4 und EF


Wenn es mal wieder schnell gehen soll und man nicht viel Zeit in eine eigene Benutzerverwaltung investieren möchte, dann ist man beim SimpleMembership Provider für ASP.NET Projekte genau richtig.

Aber was macht bzw. ist der SimpleMembership Provider eigentlich?

Wenn man eine neue ASP.NET MVC4 Webseite für das Internet erstellt, dann ist hier bereits der SimpleMembership Provider “enthalten” und muss nur noch entsprechend konfiguriert werden. Dabei stellt der Provider bereits fertige Funktionen wie “WebSecurity.CreateUserAndAccount”, “WebSecurity.Login”, … zur Verfügung. Der Provider erstellt beim erstmaligen Ausführen eigene Tabellen in der angegebenen Datenbank und verwaltet damit die passenden Passwörter, Rollen, … Damit ist eine einfache Nutzerverwaltung innerhalb von ein paar Minuten konfiguriert und man hat Zugriff auf alle wichtigen Funktionen die man für eine einfache Benutzerverwaltung benötigt.

Man muss bei älteren Webprojekten zusätzlich aufpassen, denn es gibt einen “alten” MemberShip Provider aus ASP.NET 2.0 Zeiten welcher anders implementiert wird, hier muss man die Datenbanktabellen z.B. über ein extra Tool anlegen, außerdem wird kein OAuth unterstützt.

Einen sehr ausführlichen Artikel zum Thema SimpleMembership hat z.B. Jon Galloway geschrieben, dieser Artikel verschafft einen sehr guten Überblick über die Thematik.

Einbinden des SimpleMembership Providers in ein ASP.NET MVC Projekt mit EF

Im Folgenden will ich kurz erläutern wie man den bereits “fertigen” SimplemMembership Provider in einem MVC4 Projekt mit EF einrichtet und einige “unnötige” Abhängigkeiten wie einen zusätzlichen DB Context entfernt.

Dazu erstellen wir zuerst ein neues ASP.NET MVC4 Internet Projekt.

image

In dem neu erstellten ASP.NET MVC Projekt gibt es unter Filters die Datei “InitializeSimpleMembershipAttribute.cs” hier findet man den folgenden Aufruf:


 WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

In diesen Aufruf tragen wir später unseren passenden Connectionstring und die Tabelle sowie die Spaltennamen für UserID und UserName ein.

image

Als nächstes legen wir erst einmal ein EF5 Model an in einer extra Klassenbibliothek an

image

Dafür wählen wir ein leeres Model

image

und erstellen eine neue Entität für das Model, den “Member” dieser wird später benötigt um unserem SimpleMembership Provider zu sagen wo der Username und die passende UserId stehen.

image

Da ich in meinen Projekten nie auf den eingebauten Connectionstring zurückgreife, sondern diesen lieber selbst “zusammenbaue” fügen wir unserer Klassenbibliothek in der sich das EF Model befindet noch eine weitere Datei “MemberModelContainer.cs” hinzu die unseren Context um einen weiteren Konstruktor erweitert. Diesem Konstruktor können wir direkt einen ConnectionString übergeben, den wir selbst zusammengebaut haben.

image

public partial class MemberModelContainer
{
    public MemberModelContainer(string connectionString) : base(connectionString)
    {
    }
}

Jetzt fügen wir unserer Webseite einen Verweis auf unser neues Projekt “Member.EF.Model” hinzu und erweitern die Web.config unter den appSettings um die folgenden Werte:


Damit wir jetzt überall auch auf unser Model zugreifen können, erstellen wir eine weitere Helper Klasse “CurrentHttpContext” die uns für den jeweiligen Request das aktuelle EF Model zur Verfügung stellt, mit den angegebenen Verbindungsdaten aus der Web.config.

public class CurrentHttpContext
{
    #region Properties
    private static string m_httpContextName = "EFDataModelHttpContextName";
    #endregion

    ///
    /// Gibt den aktuellen DataModelContainer für diesen Request zurück.
    ///
    public static MemberModelContainer GetDataModel()
    {
         //Den aktuellen dataContext nur für den aktuellen Request speichern
        if (HttpContext.Current.Items[m_httpContextName] == null)
        {
            //Connectionstring wird anhand des ModelNamens aus der web.config geladen
            MemberModelContainer efModelContainer = new MemberModelContainer(GetConnectionString("MemberModel"));
            HttpContext.Current.Items.Add(m_httpContextName, efModelContainer);
        }

        return (MemberModelContainer)HttpContext.Current.Items[m_httpContextName];
    }

    ///
    /// Gibt den Connectionstring zur DB zurück für das EntityFramework
    ///
    public static string GetConnectionString(string modelName)
    {
        //Initialize the EntityConnectionStringBuilder.
        EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
        //Providernamen setzten
        entityBuilder.Provider = "System.Data.SqlClient";
        //Connectionstring setzten
        entityBuilder.ProviderConnectionString = GetSqlBuilder().ToString();
        //Metadaten setzten.
        entityBuilder.Metadata = string.Format(@"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", modelName);
        return entityBuilder.ConnectionString;
    }

    public static SqlConnectionStringBuilder GetSqlBuilder()
    {
        SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
        //Die Zugangsdaten aus der Web.Config laden, nicht den Connectionstring verwenden.
        sqlBuilder.DataSource = ConfigurationManager.AppSettings["DBDataSource"]; ;
        sqlBuilder.InitialCatalog = ConfigurationManager.AppSettings["DBCatalog"]; ;
        sqlBuilder.UserID = ConfigurationManager.AppSettings["DBUser"];
        sqlBuilder.Password = ConfigurationManager.AppSettings["DBPassword"];
        //Wichtige Option damit mehrere Abfragen geöffnet werden können
        //um Subabfragen ausführen zu können von Untergordneten Projekten
        sqlBuilder.MultipleActiveResultSets = true;
        sqlBuilder.IntegratedSecurity = false;
        //Dient zur Identifizierung der DB Connection/Application wenn man z.B: Fehler auf dem DB Server sucht.
        sqlBuilder.ApplicationName = "MemberManagement";

        return sqlBuilder;
    }
}

Jetzt können wir überall auf unsere eigenen Modeldaten zugreifen. Als nächstes erstellen wir die passende Datenbank aus unseren Modeldaten. Um unsere DB zu erstellen, fügen wir einfach in der Global.asax unter “Application_Start” folgendes hinzu:

MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
container.Database.CreateIfNotExists();

(Privat nutze ich diese Funktion nur wenn ich entwickle, im Livebetrieb erstelle ich mir das passende SQL Script über das edmx Model und erstelle darüber meine Datenbank einmalig.)

Jetzt konfigurieren wir noch die Standardeinstellungen des SimpleMembership Providers und entfernen Code der nicht benötigt wird.

Als erstes bearbeiten wir die Datei “InitializeSimpleMembershipAttribute.cs”, hier wird einiges in die Global.asax ausgelagert, dazu gehört auch der Funktionsaufruf “LazyInitializer.EnsureInitialized”, denn diese Zeile muss nur einmal ausgeführt werden und dies lässt sich in der Global.asax im Application_Start am besten umsetzten.

Damit sieht die “InitializeSimpleMembershipAttribute.cs” am Ende folgendermaßen aus:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
    public class SimpleMembershipInitializer
    {
        public SimpleMembershipInitializer()
        {
            try
            {
                  WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, "System.Data.SqlClient", "MemberSet", "Id", "Username", autoCreateTables: true);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
            }
        }
    }
}

Wichtig ist in der Funktion “SimpleMembershipInitializer” der Aufruf für

WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, „System.Data.SqlClient“, „MemberSet“, „Id“, „Username“, autoCreateTables: true);” Hier wird unser Connectionstring angegeben, sowie der Name der Tabelle in der die Userdaten gespeichert werden und die Spalte mit der “UserId” sowie mit dem “Benutzernamen”. Diese Funktion muss entsprechend eurer eigenen Modeldaten angepasst werden. Außerdem erstellt, diese Funktion auch die passenden DB Tabellen die der SimpleMembership Provider benötigt.

Aufgrund dieser Informationen, ist der Provider dann in der Lage alle wichtigen Funktionen wie Login, Logout, Register, … uns in einem einfachen Aufruf zur Verfügung zu stellen.

Über die Klasse “WebSecurity” haben wir Zugriff auf alle wichtigen Account Funktionen und über die Klasse “Roles” haben wir Zugriff auf die Rollenverwaltung für die User, die der SimpleMembership Provider ebenfalls enthält.

Die angepasste Global.asax sieht dann folgendermaßen aus:

public class MvcApplication : System.Web.HttpApplication
{
    private static InitializeSimpleMembershipAttribute.SimpleMembershipInitializer _initializer;
    private static object _initializerLock = new object();
    private static bool _isInitialized;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();

        MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
        container.Database.CreateIfNotExists();

        LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);

    }
}

Außerdem kann der folgende Code aus der “AccountModel.cs” entfernt werden, da wir unser eigenes Model und eigenen DB Context verwenden:

public class UsersContext : DbContext
 {
        public UsersContext()
            : base("DefaultConnection")
        {
        }

        public DbSet UserProfiles { get; set; }
  }

    [Table("UserProfile")]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
    }

In der “AccountController.cs” muss noch die Funktion “ExternalLoginConfirmation” auf unser Model angepasst werden

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl)
{
    string provider = null;
    string providerUserId = null;

    if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId))
    {
        return RedirectToAction("Manage");
    }

    if (ModelState.IsValid)
    {
        // Insert a new user into the database
        using (MemberModelContainer db = CurrentHttpContext.GetDataModel())
        {
            var user = db.MemberSet.FirstOrDefault(u => u.Username.ToLower() == model.UserName.ToLower());
            // Check if user already exists
            if (user == null)
            {
                // Insert name into the profile table
                db.MemberSet.Add(new global::Member.EF.Model.Member() { Username  = model.UserName, Vorname = "", Nachname = ""});
                db.SaveChanges();

                OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
                OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
            }
        }
    }

    ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
    ViewBag.ReturnUrl = returnUrl;
    return View(model);
}

Und die Registerfunktion muss ebenfalls angepasst werden, denn hier muss der Vorname und Nachname mit in der DB eingetragen werden, sonst kommt es zu einem Fehler beim Anlegen des Users.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            //TODO  RegisterModel und View um Vorname und Nachname erweitern, dann kann dies hier mit ausgfüllt werden.
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Vorname = "TestVorname", Nachname="TestNachname" });
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Außerdem kann das Attribut “[InitializeSimpleMembership]” über der AccountController Klasse entfernt werden, denn die Initialisierung des SimpleMembership Providers, findet jetzt in der Global.asax statt.

Wenn wir jetzt unsere Webseite entsprechend ausführen und einen neuen User Registrieren, sieht unsere Datenbank dann folgendermaßen aus:

image

Die Tabelle “dbo.MemberSet” wurde von unserem EF Model angelegt und die restlichen Tabellen vom SimpleMembership Provider.

Dipendency Injection und TDD mit dem SimpleMembership Provider

Da der SimpleMembership Provider nicht über einen Konstruktor initialisiert werden kann, ist es nicht möglich diesen per DI zu initialisieren. Da ich versuche in meinen aktuellen Projekten auf DI zu setzten, überlege ich aktuell ob ich wirklich auf den SimpleMembership Provider setzte oder einfach ein eigenes UserRepository erstelle, wer hier bereits entsprechende Erfahrungen gemacht hat, würde ich mich über einen Kommentar freuen.

Um den SimpleMembership Provider zu testen, habe ich nur den folgenden Artikel gefunden, der angibt wie man den SimpleMembership Provider zumindest “testen” kann.

Auch den folgenden Artikel darüber ob man auf den SimpleMembership Provider setzten sollte fand ich zumindest sehr informativ.

Das fertige Projekt kann wie immer über meinen Codeplex Account angeschaut werden unter:

MVC –> SimpleMembershipProvider

Entity Framework 4 – Lehrgang und meine “Erfahrungen”


Ich arbeite zwar schon eine ganze Weile mit EF, aber mein bisheriges Wissen bestand zum Großteil aus Wissen was ich mir beim Problemlösen angeeignet habe. Die letzte Woche hatte ich jetzt das Vergnügen an einem MOC Kurs von MS über das Entity Framework teilzunehmen. Die Beispiele waren Teils sehr interessant, aber auch teilweise abschreckend die MS da in seiner Schulung verwendet.

Im folgenden werde ich teils nur ein Paar Stichpunkte nennen, damit ich die Ideen dahinter nicht “vergesse” und evtl. helfe ich dem einen oder anderen auch dabei, wenn ich meine “Gedanken” zu den einzelnen Themen kurz anreise, nur um zu wissen wo nach man bei Google evtl. suchen muss. Es ist durchaus möglich das hier einige Themen bereits “veraltet” sind, da der Lehrgang nur auf EF4 eingeht und nur mit dem “ObjectContext” gearbeitet wird.

Bisher habe ich EF meist in Webprojekten verwendet und hier ist die Lebensdauer eines ObjectContextes nur so lange wie ein Request. Daher fallen hier einem einige Features die EF zu bieten hat gar nicht auf. Dazu gehört z.B. das Change Tracking bzw. das Vorhalten der Daten im EF, wenn diese einmal abgerufen wurden. Diese “neuen” Möglichkeiten können einen aber auch vor Probleme stellen, denn man muss von Anwendung zu Anwendung selbst entscheiden wann die Daten nur aus dem Cache (Context) geladen werden sollen und wann sollen diese frisch aus der DB geladen werden.

ObjectQuery Objekt

Ein wichtiges Element ist hier das “ObjectQuery” hier kann man bei Abfragen festlegen ob die Daten im EF Context überschrieben werden sollen, nur die Änderungen behalten werden oder ob z.B. gar keine Änderungen “NoTracking” gemerkt werden sollen (die Objekte werden vom Context “abgehängt”).

// Create a query from the entityset
ObjectQuery contacts = entities.Contacts;
contacts.MergeOption = MergeOption.NoTracking;

// Define the query
var query = from c in contacts select c;

Weitere Möglichkeiten zum Abfragen von Daten

  • Entity SQL Syntax um z.B. dynamische Abfragen zur Laufzeit zu generieren. Dabei werden Abfragen als String erstellt. Die Syntax sieht der von SQL sehr ähnlich, es werden aber die EntitySets abgefragt. Auch hier wird wieder das “ObjectQuery” Objekt verwendet
// Define the Entity SQL query
var queryString = "SELECT VALUE r from AWorksEntities.Contacts as r WHERE r.ContactID=@contactID";

// Create the query
var query = new ObjectQuery(queryString, entities);
query.MergeOption = MergeOption.OverwriteChanges;

// Add the paramenters
query.Parameters.Add(new ObjectParameter("contactID", contactID));

// Execute the query
List results = query.ToList();
  • Mit “Dynamic Linq” (Expression Trees) lassen sich auch dynamische Abfragen zur Laufzeit erstellen.
  • Stored Procedures können ebenfalls von der DB eingebunden und als Funktionen verwendet werden. Damit diese aber auch als Funktion angelegt werden, müssen diese nach dem Import erst noch für das “Model Freigegeben” werden. Dazu muss man in den Modelbrowser wechseln und im “Store” unter gespeicherte Prozeduren die passende Prozedur heraussuchen. Auch zum Speichern, Updaten oder Löschen können direkt Stored Procedures verwendet werden, diese lassen sich in den Mapping Details zur jeweiligen Klasse festlegen.

image

und mit der rechten Maustaste auf die gewünschte Prozedur gehen und “Funktionsimport hinzufügen” auswählen.

image

Im Fenster dann den Namen der Funktion festlegen, die zugehörige Stored Procedure auswählen und einen Rückgabewert auswählen den die Prozedur liefert. Jetzt kann man einfach über den Context auf die Funktion zugreifen

image

int anz = (int)entities.CountOrders(contactID).First();
  • Refresh von Datensätzen mit der Option ob die Daten aus der DB gewinnen oder die vom Client behalten werden.
entities.Refresh(RefreshMode.StoreWins, contact);
  • Prüfen ob sich einzelne Datensätze geändert haben im Model, dafür muss für jedes Einzelne Property die Eigenschaft “Parallelitätsmodus” (Concurrency Mode) von None auf Fixed gesetzt werden. Dies muss für jedes einzelne Property gesetzt werden welches wir bei Updatekonflikten überwachen wollen.
    Wenn wir das Property auf Fixed gesetzt haben und wir einen DS speichern bei dem ein Konflikt auftritt, wird eine “OptimisticConcurrencyException” geworfen die wir dann auswerten können, z.B. über ein Refresh des Entities lässt sich dieser Konflikt lösen.

image

  • Immer wenn wir SaveChanges aufrufen wird der komplette Speichervorgang für den aktuellen Context in einer Transaktion ausgeführt. Wenn man aber z.B. mehrere Contexte hat die man in einer Transaktion speichern möchte kann man den so genannten “TransactionScope” verwenden, welcher bei einem Fehler eine “TransactionAbordedException” wirft.

Performance von Abfragen “verbessern”

  • Verwenden von “Compiled Queries” um die Queries vorzucompilieren, lohnt sich aber nur, wenn es sich um complexe Queries handelt und wenn man diese nicht nur einmal im aktuellen Context verwendet.
  • Verwenden von “Compiled Views”, damit lassen sich auch die Views optimieren. Der Vorgang muss im Pre-Build des Projektes stattfinden wo sich die edmx Datei befindet. Dazu wird das Tool edmgen.exe verwendet.
  • Verwenden von Eager Loading mit Linq. Dazu muss Lazyloading für den context deaktiviert sein. Dann kann man direkt mit “.Include(“Contacts”) direkte Abhängigkeiten eines Objektes bei Bedarf mit laden.

Allgemein

  • In N-Tier Anwendungen, versenden von Daten z.B. per “Self-Tracking-Entity”, hier muss Lazyloading ausgestellt werden, da ein Serialisierer im schlimmsten Fall die Komplette DB Serialisiert.
  • Synch Framework kann zum Synchronisieren von Client Serverdaten verwenden werden.
  • WCF Data Services für EF, hier werden Abfragen per REST gestellt und Abfragen sind über einfache URL Parameter möglich

Projektupdate von EF4 .Net 4.0 auf EF5 .Net 4.5


Bei diesem Beitrag handelt es sich nur um die Erfahrungen die ich bei einem Update meiner Projekte gemacht habe. Ich musste keine weitere Recherche Unternehmen, da das Update bei mir “Problemlos” verlaufen ist. Daher werde ich hier nur die Schritte aufzählen die ich durchgeführt habe um das Update auszuführen.

Ich habe dabei ein EF4.x Projekt mit .Net 4.0 mit DBContext upgegraded.

  • Upgrade des Projektes in dem das EF Projekt enthalten ist auf .Net 4.5 in den Projekteigenschaften. EF5 läuft zwar auch mit .Net 4.0 aber hier werden z.B: keine Enums unterstützt, diese stehen nur zur Verfügung wenn auch .Net 4.5 genutzt wird.

image

  • Installation des EF5 RC für das EF Projekt mit Hilfe der NuGet Konsole (-pre steht hier für Prerelease). Ein Update war bei mir nicht möglich, hier kam eine Fehlermeldung das er das “alte” Packet nicht finden konnte obwohl ich auch EF4 mit NuGet installiert hatte. aber eine Installation war auch problemlos möglich.
PM> Install-Package -ProjectName EFDataModel EntityFramework -pre
  • Löschen des alten EF4 Eintrages aus der “packages.config”, damit dort als EF Eintrag nur noch der EF5 Eintrag enthalten ist.
  • Da ich bei mir den DBContext verwendet habe, musste ich noch die beiden Dateien “MeinEFName.Context.tt” und “MeinEFName.tt” aus dem Projekt entfernen. Denn diese müssen mit einem neuen DBContext Generator für EF5 erstellt werden. Dazu musste ich eine Erweiterung installieren “EF 5.x DbContext Generator for C#”.

image

  • Erstellen der neuen *.tt Dateien über das edmx Model “Codegenerierungselement hinzufügen” und Auswählen des passenden Code Generators für EF5. Danach waren auch die *.tt Dateien wieder auf dem aktuellen Stand.

image

image

  • Upgrade war damit abgeschlossen und alles funktionierte bei mir wieder wie vorher.

EF5 – “model first” Datenbank Updates mit VS 2012


Nach einem guten Abend an Recherchearbeit, um herauszufinden ob es möglich ist, dass man evtl. direkt im EF5 Framework oder über EF Tools die Möglichkeit hat für den “model first” Ansatz seine Datenbank nach einer Modeländerung zu aktualisieren. Habe ich leider keine Integrierte Lösung finden können. Wenn hier jemand andere Erfahrungen gemacht hat, dann freue ich mich hier sehr über einen passenden Lösungsvorschlag oder Ansatz.

Außerdem habe ich einen Feature Request auf der EF Seite dafür gestartet und ich freue mich über jeden Vote, den ich bekommen kann für den Request, Voten könnt Ihr ohne euch anzumelden auf der folgenden Seite:

http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/3065564-ef5-model-first-genrate-db-update-script

Visual Studio – SQL Schemavergleich zum Aktualisieren der Datenbank

Die einzige Lösung die ich hier aktuell vorschlagen kann, ist es den Integrierten Schemavergleich im Visual Studio für Datenbankupdates zu nutzen.

Nachteile dieser Lösung:

  • Nicht direkt in den EF Modellierungsprozess eingebunden
  • Man benötigt zwei Datenbanken – einmal die Originaldatenbank die aktualisiert werden soll und dann die neue Datenbank mit allen Updates, damit ein Vergleich erstellt werden kann mit der Originaldatenbank.

Wichtige Hinweise beim Durchführen von Modelländerungen im edmx Model

Außerdem muss darauf geachtet werden, wenn man in einem edmx Modell einer Klasse neue Member hinzufügt. Hier muss “Null” angegeben werden oder ein Default Wert hinterlegt werden. Denn sonst kann später kein Updatescript vom Schemavergleich erstellt werden. Im edmx Model können zwar Standardwerte eingestellt werden, diese werden jedoch nicht im generierten SQL eingebunden. Daher muss dies von Hand angepasst werden.

Im Folgenden lege ich für den Namen den Standardwert “Maus” fest, wenn man dann aber über

image

“Datenbank aus Modell generieren” das passende SQL Script generieren lässt,

image

dann sieht der Teil in dem die Tabelle angelegt wird folgendermaßen aus:

-- Creating table 'TiereSatz'
CREATE TABLE [dbo].[TiereSatz] (
    [ID] int IDENTITY(1,1) NOT NULL,
    [Name] nvarchar(max)  NOT NULL,
    [Art] nvarchar(max)  NOT NULL
);

Hier wird aber das Folgende Script benötigt, damit später auch ein Updatescript erstellt werden kann, denn wenn in der jeweiligen Tabelle der späteren Zieldatenbank bereits Werte enthalten sind, kann kein Update durchgeführt werden. Da z.B. schon die Tabelle existierte mit dem Member “Art” und hier bereits Werte eingetragen sind muss bei einem Update entweder der default Wert gesetzt werden oder null zugelassen sein für die neue Spalte “Name”.

-- Creating table 'TiereSatz'
CREATE TABLE [dbo].[TiereSatz] (
    [ID] int IDENTITY(1,1) NOT NULL,
    [Name] nvarchar(max)  NOT NULL default 'Maus',
    [Art] nvarchar(max)  NOT NULL
);

Der “Name” kann auch ”Null” sein, dann gibt es keine Probleme beim Hinzufügen der Spalte, wenn man im Designer “Null-Werte zulassen” auf “True” stellt, dann wird das folgende Script generiert welches auch funktioniert.

-- Creating table 'TiereSatz'
CREATE TABLE [dbo].[TiereSatz] (
    [ID] int IDENTITY(1,1) NOT NULL,
    [Name] nvarchar(max)  NULL,
    [Art] nvarchar(max)  NOT NULL
);

Erstellen des Schema Vergleiches mit Visual Studio und Updaten der DB

Sobald beide Datenbanken eingerichtet sind, zum einen die “alte” Datenbank die aktualisiert werden soll und die “neue” Datenbank in der alle Änderungen enthalten sind, dann können wir im Visual Studio im Menü einen neuen Schemavergleich erstellen.

image

Hier kann man jetzt das Ziel und die Quelldatenbank auswählen

image

Die Quelle ist bei uns die “neue” Datenbank die alle Updates enthält und das Ziel ist bei und die “alte” Datenbank in der die Änderungen durchgeführt werden sollen.

Die Auswahl der passenden DB findet über ein einfaches Menü statt.

image

und sobald beide Datenbanken ausgewählt wurden, steht die Funktion “Vergleichen” zur Verfügung

image

Wenn der Vergleich ausgeführt wurde, steht einem die Aktualisieren Funktion zur Verfügung oder das Erstellen eines SQL Scripts, welches man sich anschauen kann und dann meist schon in den ersten Zeilen erkennt ob es ein erfolgreiches update wird oder nicht.

image

Wenn man auf aktualisieren klickt, sieht man im VS dann auch ob alles geklappt hat

image

Jetzt sind beide Datenbanken auf dem „gleichen” Stand, was zumindest die Tabellen und Verknüpfungen betrifft.

Beispiel für einen Fehlschlag des Updates:

Wenn eine Aktualisierung fehlschlägt, empfehle ich das man sich das Update Script ausgeben lässt, damit man sieht wo der Fehler aufgetreten ist.

Das folgende generierte SQL Update Script kann nicht erfolgreich ausgeführt werden, da ich in der Tiertabelle die Spalte “Name” auf “Not Null” gesetzt habe. Hier kann man sehr gut erkennen wo der Fehler liegt und man kann dann im edmx Model oder dem Script für das Erstellen der neuen Datenbank die passenden Änderungen vornehmen und hier wieder testen ob ein Update möglich ist.

/*
Die Spalte "[dbo].[TiereSatz].[Vorname]" in der Tabelle "[dbo].[TiereSatz]" muss hinzugefügt werden,
besitzt jedoch keinen Standardwert und unterstützt keine NULL-Werte. Wenn die Tabelle Daten enthält,
funktioniert das ALTER-Skript nicht. Um dieses Problem zu vermeiden, müssen Sie der Spalte einen
 Standardwert hinzufügen, sie so kennzeichnen, dass NULL-Werte zulässig sind, oder die Generierung
 von intelligenten Standardwerten als Bereitstellungsoption aktivieren.
*/

IF EXISTS (select top 1 1 from [dbo].[TiereSatz])
    RAISERROR (N'Zeilen wurden erkannt. Das Schemaupdate wird beendet, da es möglicherweise zu einem Datenverlust kommt.', 16, 127) WITH NOWAIT
GO

EF4 SQL Abfragen anzeigen für ObjectContext & DbContext


Wer häufig mit dem Entity Framework arbeitet kommt doch hin und wieder mal in die Verlegenheit und möchte gern wissen welche SQL Abfrage hier genau an den Server gesendet wurde. Wenn man hier nicht stolzer Besitzer eines Visual Studios Ultimate mit Intellitrace ist, muss man sich auf andere Weiße weiterhelfen.

Zum einen gibt es den klassischen ObjectContext und den DbContext und für beide Varianten gibt es die unterschiedlichsten Möglichkeiten sich die jeweiligen SQL Abfragen anzuzeigen.

Der ObjectContext

Hier gibt es keine native Möglichkeit sich den SQL Code anzuzeigen. Aber es gibt eine sehr einfache Lösung mit Hilfe einer Extension Methode. Die habe ich im MSDN Forum gefunden:

http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/2a50ffd2-ed73-411d-82bc-c9c564623cb4/

Im Post findet man die Folgenden beiden Funktionen, mit denen man problemlos die jeweiligen SQL Befehle auslesen kann. Wer Probleme hat diese zum Laufen zu bekommen, evtl. mal in den Kommentaren im MSDN Forum weiterlesen, diese haben mir auch geholfen.

 public static string ToTraceString(this IQueryable query)
        {
            System.Reflection.MethodInfo toTraceStringMethod = query.GetType().GetMethod("ToTraceString");

            if (toTraceStringMethod != null)
                return toTraceStringMethod.Invoke(query, null).ToString();
            else
                return "";
        }
        
        public static string ToTraceString(this ObjectContext ctx)
        {
            Assembly entityAssemly = Assembly.Load(entityAssemblyName);

            Type updateTranslatorType = entityAssemly.GetType(
                "System.Data.Mapping.Update.Internal.UpdateTranslator");

            Type functionUpdateCommandType = entityAssemly.GetType(
                "System.Data.Mapping.Update.Internal.FunctionUpdateCommand");

            Type dynamicUpdateCommandType = entityAssemly.GetType(
                "System.Data.Mapping.Update.Internal.DynamicUpdateCommand");

            object[] ctorParams = new object[]
                        {
                            ctx.ObjectStateManager,
                            ((EntityConnection)ctx.Connection).GetMetadataWorkspace(),
                            (EntityConnection)ctx.Connection,
                            ctx.CommandTimeout
                        };

            object updateTranslator = Activator.CreateInstance(updateTranslatorType,
                BindingFlags.NonPublic | BindingFlags.Instance, null, ctorParams, null);

            MethodInfo produceCommandsMethod = updateTranslatorType
                .GetMethod("ProduceCommands", BindingFlags.Instance | BindingFlags.NonPublic);
            object updateCommands = produceCommandsMethod.Invoke(updateTranslator, null);

            List<DbCommand> dbCommands = new List<DbCommand>();

            foreach (object o in (IEnumerable)updateCommands)
            {
                if (functionUpdateCommandType.IsInstanceOfType(o))
                {
                    FieldInfo m_dbCommandField = functionUpdateCommandType.GetField(
                        "m_dbCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    dbCommands.Add((DbCommand)m_dbCommandField.GetValue(o));
                }
                else if (dynamicUpdateCommandType.IsInstanceOfType(o))
                {
                    MethodInfo createCommandMethod = dynamicUpdateCommandType.GetMethod(
                        "CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    object[] methodParams = new object[]
                    {
                        updateTranslator,
                        new Dictionary<int, object>()
                    };

                    dbCommands.Add((DbCommand)createCommandMethod.Invoke(o, methodParams));
                }
                else
                {
                    throw new NotSupportedException("Unknown UpdateCommand Kind");
                }
            }


            StringBuilder traceString = new StringBuilder();
            foreach (DbCommand command in dbCommands)
            {
                traceString.AppendLine("=============== BEGIN COMMAND ===============");
                traceString.AppendLine();

                traceString.AppendLine(command.CommandText);
                foreach (DbParameter param in command.Parameters)
                {
                    traceString.AppendFormat("{0} = {1}", param.ParameterName, param.Value);
                    traceString.AppendLine();
                }

                traceString.AppendLine();
                traceString.AppendLine("=============== END COMMAND ===============");
            }

            return traceString.ToString();
        }

Hier ist es nun möglich die SQL Abfragen für Selects zu sehen und auch die Abfragen für Update, Insert und Delete. Das ganze kann dann z.B. folgendermaßen eingesetzt werden:

        public static void ObjectContextTrace()
        {
            //Neues Datenmodell erstellen, Achtung es muss nur ConnectionString in der App.Config "ModelContainer" angepasst werden.
            //die DB wird dann von "allein" angelegt.
            ModelContainer model = new ModelContainer();

            //Das Anlegen eines Objektes auslesen als SQL Statement
            Person person = new Person() { Name = "Müller", Vorname = "Max" };
            model.PersonMenge.AddObject(person);
            Console.Write(model.ToTraceString());
            model.SaveChanges();
            Console.ReadLine();

            //Eine Abfrage auslesen als SQL Statement.
            var namen = from ePerson in model.PersonMenge where ePerson.Name == "Müller" select ePerson;
            Console.Write(namen.ToTraceString());
            Console.ReadLine();
        }

Der DbContext

Um sich die SQL Abfragen für einen DBContext anzusehen, bietet .NET für Select Abfragen bereits eine mitgelieferte Möglichkeit, hier kann einfach “ToString()” auf das passende Query angewendet werden:

//Abfrage erstellen
var houses = from eHouse in modelDbContext.HausMenge where eHouse.Nummer == "31a" select eHouse;
Console.Write(houses.ToString());

Möchte man hier auch Update, Insert und Delete SQL Statements angezeigt bekommen, muss man einen Tracing und Caching Provider fürs das EF Installieren. Dieser steht auf CodePlex zum Download mittels NuGet bereit und muss unserem Projekt hinzugefügt werden. Auf der Homeseite von CodePlex ist in einfachen vier Schritten erläutert was genau gemacht werden muss, damit man die SQL Ausgaben bekommt.

Bei mir sieht das ganze dann z.B: folgendermaßen aus:

 public static void DbContextTrace()
 {
      //Einstellen wo das Log ausgegeben werden soll
      EFTracingProviderConfiguration.LogToConsole = true;

      //http://code.msdn.microsoft.com/EFProviderWrappers
      //Neues Datenmodell erstellen, Achtung es muss nur ConnectionString in der App.Config "ModelContainer" angepasst werden.
      ModelDbContextContainer modelDbContext = new ModelDbContextContainer("ModelDbContextContainer");
      //Beim Ersten ausführen auskommentieren und evtl. mit CreateDB ersetzten, macht probleme mit dem Wrapper
      //modelDbContext.Database.CreateIfNotExists();

      //Neues Objekt erstellen und der DB hinzufügen
      Haus haus = new Haus() { Nummer = "31a", Strasse = "Altenberger Straße" };
      modelDbContext.HausMenge.Add(haus);
       //Wird auf der Konsole ausgegeben, wenn Save Changes aufgerufen wird.
       modelDbContext.SaveChanges();
}

Wenn man jetzt die Ausgabe im Visual Studio beachtet, dann wird dort ausgegeben welche SQL Statement abgesetzt wurden und wie lange die jeweilige Ausführung gedauert hat. Auch Select Anweisungen sind hier zu finden, aber erst wenn diese auch wirklich ausgeführt wurden.

image

Mein Beispielprojekt auf CodePlex

Ich habe auch für dieses Beispiel ein Projekt auf CodePlex angelegt, welches man folgendermaßen finden kann:

https://squadwuschel.codeplex.com/

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “EFTraceTest”

Hier könnt Ihr den kompletten Quelltext finden. Wenn jemand noch andere bzw. einfachere Möglichkeiten weiß wie man an die SQL Statements von EF Abfragen kommt, dann nicht zögern einen Kommentar abzugeben.