Elastic Database-klientbibliotek med Entity Framework

Gäller för:Azure SQL Database

Det här dokumentet visar ändringarna i ett Entity Framework-program som behövs för att integrera med Elastic Database-verktygen. Fokus ligger på att skapa hantering av fragmentkartor och databeroende routning med entity framework code first-metoden. Självstudiekursen Code First – New Database för EF fungerar som det exempel som körs i hela det här dokumentet. Exempelkoden som medföljer det här dokumentet är en del av de elastiska databasverktygens uppsättning exempel i Visual Studio Code-exempel.

Kommentar

Den här artikeln gäller inte för Entity Framework Core (EF Core).

Ladda ned och köra exempelkoden

Så här laddar du ned koden för den här artikeln:

Om du vill köra exemplet måste du skapa tre tomma databaser i Azure SQL Database:

  • Shard Map Manager-databas
  • Shard 1-databas
  • Shard 2-databas

När du har skapat dessa databaser fyller du i platshållarna i Program.cs med servernamnet, databasnamnen och dina autentiseringsuppgifter för att ansluta till databaserna. Skapa lösningen i Visual Studio. Visual Studio laddar ned nödvändiga NuGet-paket för klientbiblioteket för elastisk databas, Entity Framework och hantering av tillfälliga fel som en del av byggprocessen. Kontrollera att återställning av NuGet-paket är aktiverat för din lösning. Du kan aktivera den här inställningen genom att högerklicka på lösningsfilen i Visual Studio Solution Explorer.

Entity Framework-arbetsflöden

Entity Framework-utvecklare förlitar sig på något av följande fyra arbetsflöden för att skapa program och säkerställa beständighet för programobjekt:

  • Kod först (ny databas): EF-utvecklaren skapar modellen i programkoden och sedan genererar EF databasen från den.
  • Code First (befintlig databas): Utvecklaren låter EF generera programkoden för modellen från en befintlig databas.
  • Modell först: Utvecklaren skapar modellen i EF-designern och sedan skapar EF databasen från modellen.
  • Databas först: Utvecklaren använder EF-verktyg för att härleda modellen från en befintlig databas.

Alla dessa metoder förlitar sig på klassen DbContext för att transparent hantera databasanslutningar och databasscheman för ett program. Olika konstruktorer i basklassen DbContext ger olika kontrollnivåer över skapande av anslutningar, databasstövlar och schemaskapande. Utmaningar uppstår främst på grund av att den databasanslutningshantering som tillhandahålls av EF samverkar med anslutningshanteringsfunktionerna i de databeroende routningsgränssnitten som tillhandahålls av klientbiblioteket för elastiska databaser.

Antaganden om elastiska databasverktyg

Termdefinitioner finns i Elastic Database-verktygsordlista.

Med klientbiblioteket för elastisk databas definierar du partitioner av dina programdata som kallas shardletar. Shardletar identifieras av en horisontell partitioneringsnyckel och mappas till specifika databaser. Ett program kan ha så många databaser som behövs och distribuera shardlets för att ge tillräckligt med kapacitet eller prestanda med tanke på aktuella affärskrav. Mappningen av nyckelvärden för horisontell partitionering till databaserna lagras av en shardkarta som tillhandahålls av klient-API:erna för elastisk databas. Den här funktionen kallas Shard Map Management eller SMM för kort. Shard-kartan fungerar också som koordinator för databasanslutningar för begäranden som har en partitioneringsnyckel. Den här funktionen kallas för databeroende routning.

Shard Map Manager skyddar användare från inkonsekventa vyer till shardlet-data som kan inträffa när samtidiga shardlet-hanteringsåtgärder (till exempel att flytta data från en shard till en annan) sker. För att göra det mappar shard som hanteras av klientbiblioteket asynkrona databasanslutningarna för ett program. På så sätt kan funktionerna för horisontell mappning automatiskt avsluta en databasanslutning när shardhanteringsåtgärder kan påverka den shardlet som anslutningen har skapats för. Den här metoden måste integreras med vissa av EF:s funktioner, till exempel att skapa nya anslutningar från en befintlig för att söka efter databasens existens. I allmänhet har vår observation varit att standard-DbContext-konstruktorerna endast fungerar tillförlitligt för stängda databasanslutningar som säkert kan klonas för EF-arbete. Designprincipen för elastisk databas är i stället att endast asynkrona öppnade anslutningar. Man kan tro att det kan lösa problemet genom att stänga en anslutning som asynkronas av klientbiblioteket innan den överlämnas till EF DbContext. Men genom att stänga anslutningen och förlita sig på ef för att öppna den igen avstår man från de verifierings- och konsekvenskontroller som utförs av biblioteket. Migreringsfunktionen i EF använder dock dessa anslutningar för att hantera det underliggande databasschemat på ett sätt som är transparent för programmet. Helst behåller och kombinerar du alla dessa funktioner från både klientbiblioteket för elastisk databas och EF i samma program. I följande avsnitt beskrivs dessa egenskaper och krav mer detaljerat.

Behov

När du arbetar med både klientbiblioteket för elastisk databas och Entity Framework-API:er vill du behålla följande egenskaper:

  • Skala ut: Lägga till eller ta bort databaser från datanivån för det fragmenterade programmet efter behov för programmets kapacitetskrav. Det innebär kontroll över skapande och borttagning av databaser och användning av API:er för shard map manager för elastisk databas för att hantera databaser och mappningar av shardletar.
  • Konsekvens: Programmet använder horisontell partitionering och använder de databeroende routningsfunktionerna i klientbiblioteket. För att undvika skada eller fel frågeresultat asynkronas anslutningar via shard map manager. Detta behåller också validering och konsekvens.
  • Kod först: För att behålla bekvämligheten med EF:s kod första paradigm. I Kod först mappas klasser i programmet transparent till de underliggande databasstrukturerna. Programkoden interagerar med DbSets som maskerar de flesta aspekter som ingår i den underliggande databasbearbetningen.
  • Schema: Entity Framework hanterar inledande skapande av databasscheman och efterföljande schemautveckling genom migreringar. Genom att behålla de här funktionerna är det enkelt att anpassa din app allt eftersom data utvecklas.

Följande vägledning beskriver hur du uppfyller dessa krav för Code First-program med hjälp av elastiska databasverktyg.

Databeroende routning med EF DbContext

Databasanslutningar med Entity Framework hanteras vanligtvis via underklasser av DbContext. Skapa dessa underklasser genom att härleda från DbContext. Det är här du definierar dina DbSets som implementerar databasbaserade samlingar av CLR-objekt för ditt program. I samband med databeroende routning kan du identifiera flera användbara egenskaper som inte nödvändigtvis finns för andra scenarier för EF-kod första program:

  • Databasen finns redan och har registrerats i den elastiska databasshardkartan.
  • Programmets schema har redan distribuerats till databasen (förklaras nedan).
  • Databeroende routningsanslutningar till databasen asynkronas av shardkartan.

Så här integrerar du DbContexts med databeroende routning för utskalning:

  1. Skapa fysiska databasanslutningar via klientgränssnitten för elastisk databas i shard map manager.
  2. Omslut anslutningen med underklassen DbContext
  3. Skicka anslutningen till DbContext-basklasserna för att säkerställa att all bearbetning på EF-sidan också sker.

Följande kodexempel illustrerar den här metoden. (Den här koden finns också i det tillhörande Visual Studio-projektet)

public class ElasticScaleContext<T> : DbContext
{
public DbSet<Blog> Blogs { get; set; }
...

    // C'tor for data-dependent routing. This call opens a validated connection
    // routed to the proper shard by the shard map manager.
    // Note that the base class c'tor call fails for an open connection
    // if migrations need to be done and SQL credentials are used. This is the reason for the
    // separation of c'tors into the data-dependent routing case (this c'tor) and the internal c'tor for new shards.
    public ElasticScaleContext(ShardMap shardMap, T shardingKey, string connectionStr)
        : base(CreateDDRConnection(shardMap, shardingKey, connectionStr),
        true /* contextOwnsConnection */)
    {
    }

    // Only static methods are allowed in calls into base class c'tors.
    private static DbConnection CreateDDRConnection(
    ShardMap shardMap,
    T shardingKey,
    string connectionStr)
    {
        // No initialization
        Database.SetInitializer<ElasticScaleContext<T>>(null);

        // Ask shard map to broker a validated connection for the given key
        SqlConnection conn = shardMap.OpenConnectionForKey<T>
                            (shardingKey, connectionStr, ConnectionOptions.Validate);
        return conn;
    }

Huvudpunkter

  • En ny konstruktor ersätter standardkonstruktorn i underklassen DbContext

  • Den nya konstruktorn tar de argument som krävs för databeroende routning via klientbiblioteket för elastisk databas:

    • shardkartan för att få åtkomst till de databeroende routningsgränssnitten,
    • partitioneringsnyckeln för att identifiera shardleten,
    • en anslutningssträng med autentiseringsuppgifterna för den databeroende routningsanslutningen till shard.
  • Anropet till basklasskonstruktorn tar en omväg till en statisk metod som utför alla steg som krävs för databeroende routning.

    • Den använder Open Anslut ionForKey-anropet för klientgränssnitten för elastisk databas på fragmentkartan för att upprätta en öppen anslutning.
    • Shard-kartan skapar den öppna anslutningen till fragmentet som innehåller shardleten för den angivna partitioneringsnyckeln.
    • Den här öppna anslutningen skickas tillbaka till basklasskonstruktorn i DbContext för att indikera att den här anslutningen ska användas av EF i stället för att EF ska kunna skapa en ny anslutning automatiskt. På så sätt har anslutningen taggats av klient-API:et för elastisk databas så att den kan garantera konsekvens under hanteringsåtgärder för fragmentkarta.

Använd den nya konstruktorn för underklassen DbContext i stället för standardkonstruktorn i koden. Här är ett exempel:

// Create and save a new blog.

Console.Write("Enter a name for a new blog: ");
var name = Console.ReadLine();

using (var db = new ElasticScaleContext<int>(
                        sharding.ShardMap,  
                        tenantId1,  
                        connStrBldr.ConnectionString))
{
    var blog = new Blog { Name = name };
    db.Blogs.Add(blog);
    db.SaveChanges();

    // Display all Blogs for tenant 1
    var query = from b in db.Blogs
                orderby b.Name
                select b;
    …
}

Den nya konstruktorn öppnar anslutningen till fragmentet som innehåller data för den shardlet som identifieras av värdet för tenantid1. Koden i användningsblocket förblir oförändrad för att komma åt DbSet för bloggar med EF på shard för tenantid1. Detta ändrar semantiken för koden i användningsblocket så att alla databasåtgärder nu är begränsade till den shard där tenantid1 behålls. Till exempel skulle en LINQ-fråga via bloggarna DbSet bara returnera bloggar som lagras på den aktuella shard, men inte de som lagras på andra shards.

Hantering av tillfälliga fel

Microsoft Patterns &Practices-teamet publicerade programblocket för övergående felhantering. Biblioteket används med elastiskt skalningsklientbibliotek i kombination med EF. Se dock till att ett tillfälligt undantag återgår till en plats där du kan se till att den nya konstruktorn används efter ett tillfälligt fel så att alla nya anslutningsförsök görs med konstruktorerna som du har justerat. I annat fall garanteras inte en anslutning till rätt shard och det finns inga garantier för att anslutningen upprätthålls när ändringar i shardkartan sker.

Följande kodexempel illustrerar hur en SQL-återförsöksprincip kan användas runt de nya underklasskonstruktorerna i DbContext :

SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
    using (var db = new ElasticScaleContext<int>(
                            sharding.ShardMap,  
                            tenantId1,  
                            connStrBldr.ConnectionString))
        {
                var blog = new Blog { Name = name };
                db.Blogs.Add(blog);
                db.SaveChanges();
        …
        }
    });

SqlDatabaseUtils.SqlRetryPolicy i koden ovan definieras som en SqlDatabaseTransientErrorDetectionStrategy med ett återförsöksantal på 10 och 5 sekunders väntetid mellan återförsök. Den här metoden liknar vägledningen för EF och användarinitierade transaktioner (se Begränsningar med strategier för återförsökskörning (EF6 och senare). Båda situationerna kräver att programprogrammet styr det omfång som det tillfälliga undantaget returnerar till: antingen för att öppna transaktionen igen eller (som visas) återskapa kontexten från rätt konstruktor som använder klientbiblioteket för elastisk databas.

Behovet av att kontrollera var tillfälliga undantag tar oss tillbaka i omfånget utesluter också användningen av den inbyggda SqlAzureExecutionStrategy som medföljer EF. SqlAzureExecutionStrategy öppnar en anslutning igen men använder inte Open Anslut ionForKey och kringgår därför all validering som utförs som en del av anropet Open Anslut ionForKey. I stället använder kodexemplet den inbyggda DefaultExecutionStrategy som också medföljer EF. Till skillnad från SqlAzureExecutionStrategy fungerar det korrekt i kombination med återförsöksprincipen från Tillfälliga felhantering. Körningsprincipen anges i klassen ElasticScaleDbConfiguration . Observera att vi bestämde oss för att inte använda DefaultSqlExecutionStrategy eftersom det föreslår att du använder SqlAzureExecutionStrategy om tillfälliga undantag inträffar , vilket skulle leda till fel beteende enligt beskrivningen. Mer information om de olika återförsöksprinciperna och EF finns i Anslut ionsåterhämtning i EF.

Omskrivningar av konstruktor

Kodexemplen ovan illustrerar standardkonstruktorns omskrivningar som krävs för ditt program för att kunna använda databeroende routning med Entity Framework. I följande tabell generaliseras den här metoden för andra konstruktorer.

Aktuell konstruktor Omskriven konstruktor för data Baskonstruktor Kommentar
MyContext() ElasticScaleContext(ShardMap, TKey) DbContext(Db Anslut ion, bool) Anslutningen måste vara en funktion av fragmentkartan och den databeroende routningsnyckeln. Du måste skapa automatisk anslutning via direktanslutning via EF och i stället använda shardkartan för att asynkrona anslutningen.
MyContext(sträng) ElasticScaleContext(ShardMap, TKey) DbContext(Db Anslut ion, bool) Anslutningen är en funktion av fragmentkartan och den databeroende routningsnyckeln. Ett fast databasnamn eller anslutningssträng fungerar inte eftersom de godkänns av shardkartan.
MyContext(DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(Db Anslut ion, DbCompiledModel, bool) Anslutningen skapas för den angivna fragmentkartan och partitioneringsnyckeln med den angivna modellen. Den kompilerade modellen skickas vidare till baskonstruktorn.
MyContext(Db Anslut ion, bool) ElasticScaleContext(ShardMap, TKey, bool) DbContext(Db Anslut ion, bool) Anslutningen måste härledas från fragmentkartan och nyckeln. Det kan inte anges som indata (såvida inte indata redan använde shardkartan och nyckeln). Det booleska objektet skickas vidare.
MyContext(string, DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(Db Anslut ion, DbCompiledModel, bool) Anslutningen måste härledas från fragmentkartan och nyckeln. Det går inte att ange som indata (såvida inte indata använde shardkartan och nyckeln). Den kompilerade modellen skickas vidare.
MyContext(ObjectContext, bool) ElasticScaleContext(ShardMap, TKey, ObjectContext, bool) DbContext(ObjectContext, bool) Den nya konstruktorn måste se till att alla anslutningar i ObjectContext som skickas som indata dirigeras om till en anslutning som hanteras av Elastic Scale. En detaljerad diskussion om ObjectContexts ligger utanför omfånget för det här dokumentet.
MyContext(Db Anslut ion, DbCompiledModel, bool) ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool) DbContext(Db Anslut ion, DbCompiledModel, bool); Anslutningen måste härledas från fragmentkartan och nyckeln. Anslutningen kan inte anges som indata (såvida inte indata redan använde shardkartan och nyckeln). Modell och boolesk skickas vidare till basklasskonstruktorn.

Shard-schemadistribution via EF-migreringar

Automatisk schemahantering är en bekvämlighet som tillhandahålls av Entity Framework. När det gäller program som använder elastiska databasverktyg vill du behålla den här funktionen för att automatiskt etablera schemat till nyligen skapade shards när databaser läggs till i det fragmenterade programmet. Det primära användningsfallet är att öka kapaciteten på datanivån för fragmenterade program med ef. Att förlita sig på EF:s funktioner för schemahantering minskar databasadministrationsarbetet med ett fragmenterat program som bygger på EF.

Schemadistribution via EF-migreringar fungerar bäst på oöppnade anslutningar. Detta står i kontrast till scenariot för databeroende routning som förlitar sig på den öppna anslutningen som tillhandahålls av klient-API:et för elastisk databas. En annan skillnad är konsekvenskravet: Även om det är önskvärt att säkerställa konsekvens för alla databeroende routningsanslutningar för att skydda mot samtidig shardkartamanipulering, är det inte ett problem med den inledande schemadistributionen till en ny databas som ännu inte har registrerats på fragmentkartan och ännu inte allokerats för att lagra shardletar. Du kan därför förlita dig på regelbundna databasanslutningar för det här scenariot, till skillnad från databeroende routning.

Detta leder till en metod där schemadistribution via EF-migreringar är nära kopplat till registreringen av den nya databasen som en shard i programmets fragmentkarta. Detta är beroende av följande krav:

  • Databasen har redan skapats.
  • Databasen är tom – den innehåller inget användarschema och inga användardata.
  • Databasen kan ännu inte nås via klient-API:erna för elastisk databas för databeroende routning.

Med dessa förutsättningar på plats kan du skapa en vanlig oöppnad Sql Anslut ion för att starta EF-migreringar för schemadistribution. Följande kodexempel illustrerar den här metoden.

// Enter a new shard - i.e. an empty database - to the shard map, allocate a first tenant to it  
// and kick off EF initialization of the database to deploy schema

public void RegisterNewShard(string server, string database, string connStr, int key)
{

    Shard shard = this.ShardMap.CreateShard(new ShardLocation(server, database));

    SqlConnectionStringBuilder connStrBldr = new SqlConnectionStringBuilder(connStr);
    connStrBldr.DataSource = server;
    connStrBldr.InitialCatalog = database;

    // Go into a DbContext to trigger migrations and schema deployment for the new shard.
    // This requires an un-opened connection.
    using (var db = new ElasticScaleContext<int>(connStrBldr.ConnectionString))
    {
        // Run a query to engage EF migrations
        (from b in db.Blogs
            select b).Count();
    }

    // Register the mapping of the tenant to the shard in the shard map.
    // After this step, data-dependent routing on the shard map can be used

    this.ShardMap.CreatePointMapping(key, shard);
}

Det här exemplet visar metoden RegisterNewShard som registrerar fragmentet i fragmentkartan, distribuerar schemat via EF-migreringar och lagrar en mappning av en horisontell partitioneringsnyckel till shard- Den förlitar sig på en konstruktor i underklassen DbContext (ElasticScaleContext i exemplet) som tar en SQL-anslutningssträng som indata. Koden för den här konstruktorn är rak, vilket visas i följande exempel:

// C'tor to deploy schema and migrations to a new shard
protected internal ElasticScaleContext(string connectionString)
    : base(SetInitializerForConnection(connectionString))
{
}

// Only static methods are allowed in calls into base class c'tors
private static string SetInitializerForConnection(string connectionString)
{
    // You want existence checks so that the schema can get deployed
    Database.SetInitializer<ElasticScaleContext<T>>(
new CreateDatabaseIfNotExists<ElasticScaleContext<T>>());

    return connectionString;
}

Man kan ha använt den version av konstruktorn som ärvts från basklassen. Men koden måste se till att standardinitieraren för EF används vid anslutning. Därav den korta omväg till den statiska metoden innan du anropar till basklasskonstruktorn med anslutningssträng. Observera att registreringen av shards ska köras i en annan appdomän eller process för att säkerställa att initieringsinställningarna för EF inte står i konflikt.

Begränsningar

De metoder som beskrivs i det här dokumentet medför ett par begränsningar:

  • EF-program som använder LocalDb måste först migrera till en vanlig SQL Server-databas innan du använder klientbiblioteket för elastisk databas. Det går inte att skala ut ett program via horisontell partitionering med elastisk skalning med LocalDb. Observera att utveckling fortfarande kan använda LocalDb.
  • Alla ändringar i programmet som innebär ändringar i databasschemat måste gå igenom EF-migreringar på alla shards. Exempelkoden för det här dokumentet visar inte hur du gör detta. Överväg att använda Update-Database med en Anslut ionString-parameter för att iterera över alla shards, eller extrahera T-SQL-skriptet för den väntande migreringen med alternativet -Script och tillämpa T-SQL-skriptet på dina shards.
  • Med tanke på en begäran antas det att all dess databasbearbetning finns i en enda shard som identifieras av partitioneringsnyckeln som tillhandahålls av begäran. Det här antagandet är dock inte alltid sant. Om det till exempel inte går att göra en horisontell partitioneringsnyckel tillgänglig. För att åtgärda detta tillhandahåller klientbiblioteket klassen MultiShardQuery som implementerar en anslutningsabstraktion för att fråga över flera shards. Att lära sig använda MultiShardQuery i kombination med EF ligger utanför omfånget för det här dokumentet

Slutsats

Genom de steg som beskrivs i det här dokumentet kan EF-program använda klientbibliotekets kapacitet för elastisk databas för databeroende routning genom att omstrukturera konstruktorer för dbContext-underklasserna som används i EF-programmet. Detta begränsar de ändringar som krävs för de platser där DbContext-klasser redan finns. Dessutom kan EF-program fortsätta att dra nytta av automatisk schemadistribution genom att kombinera de steg som anropar nödvändiga EF-migreringar med registrering av nya shards och mappningar i fragmentkartan.

Ytterligare resurser

Använder du inte elastiska databasverktyg än? Kolla in vår komma igång-guide. Om du har frågor kan du kontakta oss på microsofts Q&A-frågesida för SQL Database och för funktionsförfrågningar, lägga till nya idéer eller rösta på befintliga idéer i SQL Database-feedbackforumet.