Initialisere databasen med testdata. Opprette en MVC-webapplikasjon

For å skape objektmodell for en database må klasser tilordnes til enheter som er lagret i databasen. Det er tre måter å implementere en slik cast: du kan angi attributter for eksisterende objekter, du kan bruke et spesialverktøy som lar deg generere objekter automatisk og bruke verktøyet kommandolinje SQLMetal.

  • Objektrelasjonell konstruktør. Denne designeren gir multifunksjonell brukergrensesnittå lage en objektmodell fra en eksisterende database. Dette verktøyet, en del av Visual Studio IDE, er best egnet for små til mellomstore databaser.
  • SQLMetal-kodegenereringsverktøy. I henhold til parametersettet, dette konsollprogram noe forskjellig fra den objektrelasjonelle designeren. Dette verktøyet er best egnet for modellering av store databaser.
  • Kode editor. Du kan skrive egen kode Bruke Visual Studio Code Editor eller en annen kodeeditor. Denne tilnærmingen kan være svært utsatt for feil, så hvis du har en eksisterende database som kan brukes til å lage en modell ved hjelp av Object Relational Designer eller SQLMetal, anbefales det ikke. En kodeeditor blir imidlertid et verdifullt verktøy når du trenger å avgrense eller endre kode som allerede er opprettet ved hjelp av andre verktøy.

Vi bruker siste metode ved å angi attributter for eksisterende objekter:

bruker System; bruker System.Collections.Generic; bruker System.Linq; bruker System.Text; bruker System.Data.Linq; ved hjelp av System.Data.Linq.Mapping; bruker System.Data.SqlClient; navneområde LinqtoSQL ( offentlig klasse Kunde ( offentlig streng KundeID ( get; sett; ) offentlig streng By ( get; sett; ) offentlig overstyringsstreng ToString() ( returner KundeID + "\t" + By; ) ) klasse Program ( statisk tomrom Main (streng args) ( DataContext db = new DataContext (@"Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True"); var resultater = fra c i db. GetTable () hvor c.City == "Moskva" velg c; foreach (var c i resultater) Console.WriteLine("(0)\t(1)", c.CustomerID, c.City); Console.ReadKey(); ) ) )

Denne applikasjonen har en kundeklasse som viser Kunder-tabellen, og har CustomerID- og City-felt som viser feltene i den tabellen. DataContext-klasseobjektet spesifiserer inngangspunktet til databasen og har en GetTable-metode som returnerer en samling av en bestemt type, i i dette tilfellet type Kunde.

Som et resultat av å kjøre programmet, vil identifikatorene og bostedsbyene til de kundene som bor i Moskva vises på skjermen.

Den lagrede prosedyren henter de 10 dyreste produktene og deres priser fra produkttabellen:

BRUK GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER-prosedyre. AS SET ROWCOUNT 10 SELECT Products.ProductName AS TenThe MostExpensiveProducts, Products.UnitPrice FROM Products ORDER BY Products.UnitPrice DESC

For å kalle denne prosedyren fra et C#-program og vise resultatene, trenger du bare å skrive 3 linjer med kode:

bruker System; bruker System.Collections.Generic; bruker System.Linq; bruker System.Text; bruker System.Data.Linq; ved hjelp av System.Data.Linq.Mapping; navneområde LinqtoSQL ( klasse Program ( static void Main(string args) ( var db = new northwindDataContext(); foreach (var r i db.Ten_Most_Expensive_Products()) Console.WriteLine(r.TenMostExpensiveProducts + "\t" + r.UnitPrice ; Console.ReadKey(); ) ) )

Merk at inngangspunktet til databasen nå er opprettet av konstruktøren til northwindDataContext-klassen (vanligvis vil klassen bli kalt (kartfilnavn) DataContext ), som ikke lenger trenger å sendes en eksplisitt tilkoblingsstreng som en parameter.

10.1.3. ADO.NET Entity Framework

ADO.NET Entity Framework(EF) – objektorientert datatilgangsteknologi, er en objektrelasjonell kartlegging (ORM)-løsning for .NET Framework fra Microsoft. Gir muligheten til å samhandle med objekter ved å bruke både LINQ i form av LINQ til Entities og ved å bruke Entity SQL. For å lette konstruksjonen av webløsninger brukes både ADO.NET Data Services og en kombinasjon av Windows Communication Foundation og Windows Presentation Foundation, som lar deg bygge flernivåapplikasjoner ved å implementere en av malene MVC-design, MVP eller MVVM.

ADO.NET Entity Framework lar utviklere lage datatilgangsapplikasjoner som opererer på en konseptuell applikasjonsmodell i stedet for direkte på et relasjonslagringsskjema. Målet er å redusere kode- og vedlikeholdsinnsatsen for datasentriske applikasjoner. Entity Framework-applikasjoner gir følgende fordeler.

  • applikasjoner kan arbeide med en konseptuell modell mht fagområde– inkludert med arvede typer, komplekse elementer og relasjoner;
  • applikasjoner er frigjort fra strenge avhengigheter av en bestemt DBMS-kjerne eller lagringsskjema;
  • tilordninger mellom den konseptuelle modellen og det depotspesifikke skjemaet kan endres uten å endre applikasjonskoden;
  • utviklere er i stand til å jobbe med en konsistent applikasjonsobjektmodell som kan tilordnes forskjellige lagringsskjemaer som kan implementeres i ulike systemer Dataledelse;
  • flere konseptuelle modeller kan tilordnes til et enkelt lagringsskjema;
  • støtte for språkintegrerte spørringer (LINQ) gir kompileringstidskontroll av spørringssyntaks mot en konseptuell modell.
10.1.3.1. Entitetsrammekomponenter

Entity Framework er et sett med ADO.NET-teknologier som muliggjør utvikling av datadrevne applikasjoner. Arkitekter og utviklere av datasentriske applikasjoner må vurdere å oppnå to vidt forskjellige mål. De må modellere enhetene, relasjonene og logikken til forretningsproblemene som skal løses, samt arbeide med DBMS-kjernene som brukes til å lagre og hente data. Data kan distribueres på tvers av flere lagringssystemer, som hver bruker forskjellige protokoller, men selv applikasjoner som kjører på et enkelt lagringssystem må balansere kravene til lagringssystemet med kravene til å skrive effektiv, vedlikeholdbar applikasjonskode.

Med Entity Framework kan utviklere arbeide med data i form av domenespesifikke objekter og egenskaper, for eksempel klienter og deres adresser, uten å måtte få tilgang til de underliggende databasetabellene og kolonnene der dataene er lagret. Denne muligheten oppstår takket være overgangen til mer høy level en abstraksjon der utviklere kan jobbe med data ved å bruke mindre kode for å lage og vedlikeholde datasentriske applikasjoner.

Entity Framework er en komponent i .NET Framework, så Entity Framework-applikasjoner kan kjøres på alle datamaskiner som har .NET Framework 3.5 SP1 installert.

10.1.3.1.1. Anvendelse av konseptuelle modeller i praksis

Et lenge kjent og mye brukt designprinsipp i datamodellering er å dele datamodellen inn i følgende tre deler: den konseptuelle modellen, den logiske modellen og den fysiske modellen. En konseptuell modell definerer enhetene og relasjonene i systemet som modelleres. Den logiske modellen for en relasjonsdatabase normaliserer enheter og relasjoner å lage tabeller med utenlandske nøkkelbegrensninger. I fysisk modell tar hensyn til egenskapene til et bestemt databehandlingssystem ved å definere databasemotorspesifikke lagringsdetaljer som partisjonering og indeksering.

Den fysiske modellen er foredlet av databaseadministratorer for å forbedre ytelsen, men programmerere som utvikler applikasjonskode er stort sett begrenset til å jobbe med logisk modell, forbereder SQL-spørringer og kaller lagrede prosedyrer. Konseptuelle modeller brukes først og fremst som et verktøy for å representere og utveksle synspunkter på søknadskrav, og fungerer derfor oftest som stort sett uforanderlige diagrammer som gjennomgås og diskuteres i de tidlige stadiene av et prosjekt, hvoretter de ikke lenger er fokus for oppmerksomhet.

Entity Framework gir mening til konseptuelle modeller ved å tillate utviklere å spørre enhetene og relasjonene i den konseptuelle modellen; Imidlertid brukes selve Entity Framework til å oversette disse operasjonene til kommandoer som er spesifikke for datakilden. Dette eliminerer behovet for at applikasjoner skal ha hardkodede avhengigheter til en spesifikk datakilde. Den konseptuelle modellen, lagringsmodellen og deres kartlegging er uttrykt i en ekstern spesifikasjon, også kjent som Entity-modellen

Ha en fin dag alle sammen. Alexey Gulynin er i kontakt. I tidligere artikler har vi sett på ulike tilnærminger til å jobbe med Entity Framework. I denne artikkelen vil jeg gjerne fortelle deg hvordan arbeid med data i Entity Framework. Vurder følgende operasjoner:

  1. Legger til en oppføring
  2. Lese oppføringer
  3. Redigere et innlegg
  4. Sletter en oppføring

La oss lage et nytt prosjekt. Denne gangen vil prosjekttypen være " Windows-applikasjon Skjemaer":

La oss legge til et DataGridView-element i skjemaet vårt. Vi vil også legge til 3 knapper: "Legg til", "Rediger", "Slett". La oss legge til 2 flere "TextBox"-elementer, der vi vil vise informasjon om oppføringen som er valgt for øyeblikket (disse 2 "TextBoxes" vil også bli brukt til å legge til en ny oppføring). La oss legge til en annen "TextBox", som vil vise informasjon om "ID" til oppføringen (dette vil være nødvendig for å redigere oppføringen). Vi vil også legge til 2 "etikett"-elementer. Til syvende og sist vil skjemaet vårt se slik ut:

Vi vil bruke «Code First»-tilnærmingen. La oss lage følgende klasse:

Offentlig klasse Land ( offentlig int Id ( get; sett; ) offentlig streng Land ( get; sett; ) offentlig streng Kapital ( get; sett; ) )

Hovedkoden vil være i filen "Form1.cs". Jeg vil gi deg all koden med en gang; den vil bli diskutert i detalj i kommentarene:

Bruke System; bruker System.Windows.Forms; bruker System.Data.Entity; bruke System.Data.Entity.Migrations; navneområde WorkWithDataInEF ( offentlig delklasse Form1: Form ( privat MyModel db; public Form1() ( // Lag vårt kontekstobjekt db = new MyModel(); InitializeComponent(); // Last inn data fra tabellen inn i cachen db.Countries. Load( ); // Bind dataene til dataGridView dataGridView1.DataSource = db.Countries.Local.ToBindingList(); ) // Hendelse: klikk på en tabellcelle privat void dataGridView1_CellClick(objektsender, DataGridViewCellEventArgs e) ( // Kontrollerer utvalg av rader // Hvis raden ikke er valgt, skjer ingenting videre hvis (dataGridView1.CurrentRow == null) returnerer; // Hent den valgte raden og cast den som Countries Countries country = dataGridView1.CurrentRow.DataBoundItem som Countries; / / Hvis vi klikker på tom linje, så gjør vi ikke noe mer hvis (land == null) returnerer; // Vis data om landet og hovedstaden i tekstboksen tb_Country.Text = country.Country; tb_Capital.Text = country.Capital; tB_ID.Text = country.Id.ToString(); ) // Legge til en post privat void btn_Add_Click(objektsender, EventArgs e) ( // Sjekk at tekstfeltene har data if (tb_Country.Text == String.Empty || tb_Capital.Text == String.Empty) ( MessageBox. Show("Fyll inn landdataene!"); return; ) // Lag en forekomst av klassen Countries, // det vil si at vi får data om landet vårt fra tekstfeltene Countries country = new Countries ( Country = tb_Country. Text, Capital = tb_Capital. Text ); // Vi legger inn data i tabellen vår db.Countries.Add(country); // Pass på å lagre endringene db.SaveChanges(); // Oppdater dataGridView slik at den vises nytt land dataGridView1.Refresh(); // Tilbakestill tekstfelt tb_Country.Text = String.Empty; tb_Capital.Text = String.Empty; tB_ID.Text = String.Empty; ) // Redigere en post privat void btn_Edit_Click(objektsender, EventArgs e) ( // Sjekk at posten er valgt hvis (tB_ID.Text == String.Empty) returnerer; // Hent id fra tekstfeltet int id = Convert.ToInt32( tB_ID.Text); // Finn landet ved å bruke denne ID-en ved å bruke Find()-metoden Countries country = db.Countries.Find(id); if (country == null) return; country.Country = tb_Country. Tekst; country.Capital = tb_Capital.Text; // Legg til eller oppdater en post db.Countries.AddOrUpdate(country); db.SaveChanges(); dataGridView1.Refresh(); ) // Fjerne en post // Alt er det samme som redigering av en post, brukes bare Remove()-metoden private void btn_delete_Click(objektsender, EventArgs e) ( if (tB_ID.Text == String.Empty) return; int id = Convert.ToInt32(tB_ID.Text); Land country = db.Countries.Find(id); if ( country == null) retur; country.Country = tb_Country.Text; country.Capital = tb_Capital.Text; db.Countries.Remove(country); db.SaveChanges() dataGridView1.Refresh(); ) ) )

Som vi ser fungerer alt.

Den mest kjente, funksjonelle og mye brukte ORM i verden .NETT er Entitetsrammeverk. Å jobbe med .NET Core versjonen ble opprettet EF kjerne. Prinsipp for operasjon EF kjerne forble den samme som forgjengerne, men dette er en annen teknologi, så forvent nå et komplett sett med funksjonalitet fra Entitetsramme 6 ikke nødvendig, men utviklingen av prosjektet fortsetter veldig aktivt. Fra denne artikkelen lærer du hvordan du raskt kan begynne å bruke Entity Framework Core V ASP.NET Core prosjekter.

Til å komme i gang med EF kjerne du må installere de nødvendige bibliotekene. La oss legge til pakkene Microsoft.EntityFrameworkCore.SqlServer og Microsoft.EntityFrameworkCore.Tools.

Først må du bestemme deg for hvilke data som skal lagres i databasen. Jeg vil legge til 2 klasser Bruker og Logg, som vil være ansvarlig for brukerdata og noen loggdata.

Offentlig klasse Bruker ( offentlig veilednings-ID ( get; sett; ) offentlig streng Fornavn ( get; sett; ) offentlig streng Etternavn ( get; sett; ) offentlig streng E-post ( get; sett; ) ) offentlig klasse Logg ( offentlig lang Id ( get ; set; ) public DateTime UtcTime ( get; set; ) public string Data ( get; set; ) )

Etter dette kan du lage en kontekst for å jobbe med databasen:

Class TestDbContext: DbContext ( public TestDbContext(DbContextOptions options) : base(options) ( ) protected override void OnModelCreating(ModelBuilder modelBuilder) ( modelBuilder.Entity() .HasIndex(b => b.Email); ) public DbSet Users ( get; sett; ) offentlige DbSet-logger ( get; sett; ) )

DbContext - denne klassen definerer datakonteksten som brukes til å jobbe med databasen.

DbSet - Representerer en samling av alle enheter av en spesifisert type som er inneholdt i en kontekst eller kan spørres fra en database.

Nå må du sette opp en tilkobling til databasen. For å gjøre dette, åpne Startup.cs-filen og legg til linjen i ConfigureServices-metoden:

Services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Vi vil sette tilkoblingsstrengen i konfigurasjonsfilen, som standard er det appsettings.json

"ConnectionStrings": ("DefaultConnection": "CONNECTION_STRING_HERE")

Database opprettelse

La oss åpne Pakkebehandlingskonsoll

Og kjør kommandoen Add-Migration InitialCreate

Dette vil lage filene som trengs for å lage databasestrukturen. De opprettede filene kan sees i den opprettede Migrations-katalogen.

Etter dette, kjør kommandoen Update-Database og databasen vil bli opprettet.

Legger til forespørselslogging

La oss nå legge til logging for alle API forespørsler. For å gjøre dette, legg til LoggingMiddleware-klassen med følgende innhold:

Offentlig klasse LoggingMiddleware ( privat skrivebeskyttet RequestDelegate _next; privat skrivebeskyttet ILogger _logger; public LoggingMiddleware(RequestDelegate neste, ILogger logger) ( _next = neste; _logger = logger; ) offentlig async Task Invoke(HttpContext)Context if dbcontext! null && context.Request.Path.ToString().ToLower().StartsWith("/api")) ( ved å bruke (var loggableResponseStream = new MemoryStream()) ( var originalResponseStream = context.Response.Body; context.Response.Body = loggableResponseStream; try ( var request = await FormatRequest(context.Request); await _next(context); var response = await FormatResponse(loggableResponseStream); loggableResponseStream.Seek(0, SeekOrigin.Begin); var newLog = new; HttpUtility.UrlDecode(context.Request.Path + context.Request.QueryString), UtcTime = DateTime.UtcNow, Data = request, Response = response, StatusCode = context.Response.StatusCode, ); await loggableResponseStream.CopyorStriginalToAsync(seorStriginalToAsync); vent dbcontext.Logs.AddAsync(newLog); dbcontext.SaveChanges(); ) catch (Exception ex) ( //Her kan du legge til feilloggingskast; ) til slutt ( context.Response.Body = originalResponseStream; ) ) ) ) privat statisk asynkron Task FormatRequest(HttpRequest request) ( request.EnableRewind(); string responseBody = new StreamReader(request.Body).ReadToEnd(); request.Body.Position = 0; return responseBody; ) privat statisk asynkron oppgave FormatResponse(Stream loggableResponseStream) ( loggableResponseStream.Position = 0; var buffer = new byteResponseStream; loggableResponseStream. ReadAsync (buffer, 0, buffer.Length); return JsonConvert.SerializeObject(Encoding.UTF8.GetString(buffer)); ) )

Og i Startup.cs-filen i Configure-metoden vil vi legge til:

App.UseMiddleware();

Etter dette vil alle forespørsler som starter med /api logges inn i databasen. Det er verdt å merke seg her at det kan være mange loggingsalternativer. Her presenterer vi kun ett av alternativene, som er enkelt å implementere og ikke krever bruk av tredjeparts biblioteker eller tjenester.

Vi vil også legge til en kontroller for å jobbe med brukere, og legge til en metode der for å få informasjon om alle brukere.

")] public class UsersController: Controller (privat skrivebeskyttet TestDbContext _context; public UsersController(TestDbContext context) ( _context = context; ) offentlig async Task GetAll() ( var users = await _context.Users.ToListAsync(); return Ok(users) ;))

Dette grunnleggende informasjon, som lar deg raskt begynne å jobbe med Entity Framework Core.

Lykke til med programmering.

Sist oppdatert: 31.10.2015

Entity Framework er en spesiell objektorientert teknologi basert på .NET-rammeverket for arbeid med data. Mens tradisjonelle ADO.NET-verktøy lar deg lage tilkoblinger, kommandoer og andre objekter for å samhandle med databaser, er Entity Framework et høyere abstraksjonsnivå som lar deg abstrahere bort fra selve databasen og arbeide med data uavhengig av lagringstype . Hvis vi på det fysiske nivået opererer med tabeller, indekser, primær- og fremmednøkler, men på det konseptuelle nivået, som Entity Framework tilbyr oss, jobber vi allerede med objekter.

Den første versjonen av Entity Framework - 1.0 ble utgitt tilbake i 2008 og ga svært begrenset funksjonalitet, grunnleggende støtte ORM (object-relational mapping - kartlegging av data til virkelige objekter) og én enkelt tilnærming til interaksjon med databasen - Database First. Med utgivelsen av versjon 4.0 i 2010 har mye endret seg - siden den gang har Entity Framework blitt den anbefalte teknologien for tilgang til data, og nye muligheter for å samhandle med databasen ble introdusert i selve rammeverket - Model First og Code Første tilnærminger.

Ytterligere funksjonalitetsforbedringer fulgte med utgivelsen av versjon 5.0 i 2012. Til slutt, i 2013, ble Entity Framework 6.0 utgitt, som har muligheten til å få tilgang til data asynkront.

Det sentrale begrepet i Entity Framework er begrepet enhet. En enhet representerer et sett med data knyttet til et spesifikt objekt. Derfor denne teknologien innebærer å jobbe ikke med tabeller, men med objekter og deres sett.

Enhver enhet, som ethvert objekt fra den virkelige verden, har en rekke egenskaper. For eksempel, hvis en enhet beskriver en person, kan vi identifisere egenskaper som fornavn, etternavn, høyde, alder, vekt. Egenskaper representerer ikke nødvendigvis enkle data skriv int, men kan også representere mer komplekse datastrukturer. Og hver enhet kan ha en eller flere egenskaper som vil skille den enheten fra andre og vil unikt definere den enheten. Slike egenskaper kalles nøkler.

I dette tilfellet kan enheter kobles sammen med en-til-mange, en-til-en og mange-til-mange assosiative relasjoner, akkurat som i en ekte database kommunikasjon skjer gjennom fremmednøkler.

Et særtrekk ved Entity Framework er bruken av LINQ-spørringer for å hente data fra databasen. Ved å bruke LINQ kan vi ikke bare hente spesifikke rader som lagrer objekter fra databasen, men også hente objekter koblet sammen med ulike assosiative lenker.

Et annet nøkkelbegrep er Entity Data Model. Denne modellen tilordner enhetsklasser til faktiske tabeller i databasen.

Entitetsdatamodellen består av tre nivåer: konseptuell, lagringsnivå og kartleggingsnivå.

På det konseptuelle nivået er enhetsklassene som brukes i applikasjonen definert.

Lagringsnivået definerer tabellene, kolonnene, relasjonene mellom tabeller og datatyper som databasen som brukes er tilordnet.

Kartleggingslaget formidler mellom de to foregående, og definerer tilordningen mellom enhetsklasseegenskaper og tabellkolonner.

Dermed kan vi samhandle med tabeller fra databasen gjennom klasser definert i applikasjonen.

Måter å samhandle med databasen

Entity Framework forutsetter tre mulige måter interaksjon med databasen:

    Database først: Entity Framework oppretter et sett med klasser som gjenspeiler modellen til en bestemt database

    Modell først: utvikleren oppretter først en databasemodell, hvorfra Entity Framework deretter oppretter den faktiske databasen på serveren.

    Kode først: utvikleren oppretter en datamodellklasse som vil bli lagret i databasen, og deretter genererer Entity Framework databasen og dens tabeller ved hjelp av denne modellen

La oss anta et ganske typisk scenario - å legge til mange objekter i databasen:

Tankeløst å slå av AutoDetectChangesEnabled-egenskapen kan føre til uønskede konsekvenser (tap av endringer, unntak på grunn av brudd på dataintegritet), så jeg vil formulere den enkleste regelen som følger: hvis koden din ikke innebærer ytterligere endringer i objekter lagt til konteksten innenfor samme økt, så kan denne egenskapen trygt deaktiveres. Denne situasjonen oppstår ganske ofte - en typisk CRUD API mottar vanligvis et objekt fra utsiden og legger det enten til, eller bestemmer også hvilke endringer som er gjort siden korrekturlesingen og oppdaterer objektets tilstandsinformasjon i konteksten tilsvarende (for eksempel ved å bruke GraphDiff , eller med bruk av selvsporede enheter, eller andre lignende løsninger). Selve objektet endres ikke.

Konstant rekompilering av noen spørringer

Fra og med Entity Framework 5, bufres spørringer automatisk etter kompilering, noe som kan fremskynde påfølgende kjøringer betydelig - tekst SQL-spørring vil bli tatt fra hurtigbufferen, er det bare å erstatte de nødvendige parameterverdiene. Men det er noen få situasjoner der kompilering vil skje ved hver utførelse.

Bruke inneholder over en samling i minnet

I praksis er det ofte behov for å legge til en betingelse i en spørring som ligner på SQL IN-operatoren - for å sjekke om verdien til en egenskap samsvarer med noen av elementene i samlingen. For eksempel slik:

Liste kanaler = ny liste (1, 5, 9); dataContext.Entities .AsNoTracking() .Where(e => channels.Contains(e.Channel)) .ToList();
Dette uttrykket blir til slutt konvertert til SQL slik:

PLUKKE UT. SOM, . SOM, . SOM FRA. SOM HVOR. IN (1, 5, 9)
Det viser seg at for IN-operatøren brukes ikke parameterne, men i stedet erstattes verdiene selv. Det vil ikke være mulig å cache en slik forespørsel, fordi Når du bruker samlingen med annet innhold, må søketeksten gjenskapes. Dette påvirker forresten ikke bare ytelsen til selve Entity Framework, men også databaseserveren, siden for enhver ny liste over verdier i IN-operatøren, vil serveren måtte gjenoppbygge og cache utførelsesplanen.

Hvis samlingen som Contains er laget for ikke forventes stort nummer elementer (f.eks. ikke mer enn hundre), kan problemet løses ved å dynamisk generere forhold knyttet til OR-operatøren. Dette er enkelt å gjøre, for eksempel ved å bruke LinqKit-biblioteket:

Liste kanaler = ny liste (1, 5, 9); var channelsCondition = PredicateBuilder.False (); channelsCondition = channels.Aggregate(channelsCondition, (current, value) => current.Eller(e => e.Channel == verdi).Expand()); var query = dataContext.Entities .AsNoTracking() .Where(channelsCondition);
Som et resultat får vi en allerede parameterisert forespørsel:

PLUKKE UT. SOM, . SOM, . SOM FRA. SOM HVOR. IN (@p__linq__0,@p__linq__1,@p__linq__2)
Selv om dynamisk konstruksjon spørringen ser ut som ekstra kostbart arbeid; i praksis tar det relativt lite prosessortid. I et virkelighetsproblem tok det mer enn et sekund å bygge en spørring hver gang den ble kalt. Og å erstatte Inneholder med et lignende dynamisk uttrykk reduserte forespørselsbehandlingstiden (bortsett fra den første) til titalls millisekunder.

Bruke Take and Skip

I mange prosjekter er det behov for å implementere personsøking for søkeresultater. Den åpenbare løsningen for å velge den nødvendige delen av poster her er Take and Skip-funksjonene:

Int sidestørrelse = 10; int startFra = 10; var query = dataContext.Entities .AsNoTracking() .OrderBy(e => e.Name) .Skip(startFrom) .Take(pageSize);
La oss se hvordan SQL-en vil være i dette tilfellet:

PLUKKE UT. SOM, . SOM, . SOM FRA. SOM BESTILLING AV . ASC OFFSET 10 RADER HENT KUN NESTE 10 RADER
Både sidestørrelsen og offsetverdien er spesifisert i forespørselen som konstanter, ikke parametere. Dette indikerer igjen at forespørselsteksten ikke vil bli bufret. Heldigvis, fra og med Entity Framework 6, er det en enkel vei rundt dette problemet - ved å bruke lambda-uttrykk i Take og Skip-funksjonene:

Var query = dataContext.Entities .AsNoTracking() .OrderBy(e => e.Name) .Skip(() => startFrom) .Take(() => pageSize);
Og den resulterende spørringen vil inneholde parametere i stedet for konstanter:

PLUKKE UT. SOM, . SOM, . SOM FRA. SOM BESTILLING AV . ASC OFFSET @p__linq__0 RADER HENT NESTE @p__linq__1 KUN RADER

Et stort antall Inkluderer i en forespørsel

Åpenbart er den enkleste måten å lese data fra databasen sammen med underordnede samlinger og andre navigasjonsegenskaper på å bruke Include()-metoden. Uavhengig av antallet Include() i en LINQ-spørring, vil det som et resultat bli generert én SQL-spørring som returnerer alle de spesifiserte dataene. Det kan virke som at innenfor Entity Framework vil denne tilnærmingen for korrekturlesing av komplekse objekter være den mest optimale i enhver situasjon. Men det er ikke slik.

La oss først se på strukturen til den endelige SQL-spørringen. For eksempel har vi en LINQ-spørring med to Includes for samlinger.

Var query = c.GuidKeySequentialParentEntities .AsNoTracking() .Include(e => e.Children1) .Include(e => e.Children2) .Where(e => e.Id == sequentialGuidKey);
Den tilsvarende SQL-en vil inneholde UNION ALL:

PLUKKE UT. SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . AS FROM (VELG CASE WHEN (. IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS , 1 AS , . AS , . AS , . AS , . AS , . AS , CAST(NULL AS uniqueidentifier) , CAST (NULL AS varchar(1)) AS , CAST(NULL AS unikidentifikator) ​​SOM FRA . SOM VENSTRE YTRE JOIN . SOM PÅ . = . HVOR . = @p__linq__0 UNION ALL SELECT 2 AS , 2 AS , . AS , . AS , CAST (NULL AS uniqueidentifier)​AS , CAST(NULL AS varchar(1)) AS , CAST(NULL AS uniqueidentifier)​AS , . AS , . AS , . FROM . AS INNER JOIN . AS ON . = . HVOR . = @p__linq__0 ) SOM BESTILLES AV . ASC,. A.S.C.
Det ville være logisk å anta at Include() ganske enkelt legger til en annen JOIN til spørringen. Men Entity Framework oppfører seg mer komplekst. Hvis den inkluderte navigasjonsegenskapen er et enkelt objekt og ikke en samling, vil det bare være en annen JOIN. Hvis det er en samling, vil det genereres en egen underspørring for hver, hvor den overordnede tabellen kobles til den underordnede tabellen, og alle slike underspørringer vil bli kombinert til en felles UNION ALL. Åpenbart, hvis bare én barnesamling er nødvendig, vil ikke UNION ALL fungere. Skjematisk kan dette avbildes som følger:

VELG /* liste over felt */ FRA (VELG /* liste over felt */ FRA /* overordnet tabell */ VENSTRE YTRE JOIN /* underordnet tabell 1 */ HVOR /* generell tilstand */ UNION ALL SELECT /* liste over felt */ FRA /* overordnet tabell */ INNER JOIN /* underordnet tabell 2 */ HVOR /* generell tilstand */ UNION ALL SELECT /* liste over felter */ FRA /* overordnet tabell */ INNER JOIN /* underordnet tabell 3 * / HVOR /* generell tilstand */ /* ... */ BESTILL ETTER /* liste over felter */
Dette ble gjort for å bekjempe problemet med å multiplisere resultater. La oss si at et objekt har tre underordnede samlinger med 10 elementer hver. Hvis alle tre legges til via OUTER JOIN direkte i "hoved"-spørringen, vil resultatet være 10 * 10 * 10 = 1000 poster. Hvis vi går ruten Entity Framework og samler disse tre samlingene i én spørring via UNION, får vi 30 poster. Jo flere samlinger og elementer det er, jo mer åpenbar er fordelen med UNION-tilnærmingen.

Men problemet er at, gitt den store kompleksiteten til selve enhetene og utvalgskriteriene, er det svært arbeidskrevende å bygge og optimalisere en slik spørring for Entity Framework, og det samme er å utføre det på databaseservernivå. Derfor, hvis profileringsresultatene viser utilfredsstillende ytelse for spørringer som inneholder Inkluder, men alt er i orden med indeksene i databasen, er det fornuftig å tenke på alternative løsninger.

Hovedideen med alternative løsninger er å lese hver samling med en separat spørring. Det enkleste alternativet er mulig hvis objekter legges til konteksten under utvelgelsen, dvs. uten å bruke AsNoTracking():

Var barn1 = c.ChildEntities1 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) var children2 = c.ChildEntities2 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) children1.Load(); barn2.Load(); var query = c.ParentEntities .Where(e => e.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .ToList();
Det viser seg at vi for hver barnesamling leser alle objektene som er relatert til de overordnede enhetene som faller inn under søkekriteriene. Etter at Load() er kalt, legges objekter til konteksten. Under lesing av overordnede enheter, vil Entity Framework finne alle underordnede enheter som er i konteksten og legge til referanser til dem deretter.

Den største ulempen her er at hver forespørsel krever et eget kall til databaseserveren. Heldigvis finnes det også en måte å løse dette problemet på. EntityFramework.Extended-biblioteket har muligheten til å lage "fremtidige" spørringer. Hovedideen er at alle forespørsler som utvidelsesmetoden Future() ble kalt vil bli sendt i ett anrop til serveren når terminalmetoden kalles på noen av dem:

Var barn1 = c.ChildEntities1 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future(); var children2 = c.ChildEntities2 .Where(e => e.Parent.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future(); var results = c.ParentEntities .Where(e => e.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -1)) .Future() .ToList();
Som et resultat, som i det første eksemplet, vil objekter fra resultatsamlingen inneholde korrekt fylte Children1- og Children2-samlinger, og alle data vil mottas i ett anrop til serveren.

Å bruke "fremtidige" søk vil være nyttig i enhver situasjon der du trenger å kjøre flere separate søk.

Trekker bare felt fra basisenheten når du bruker tabell per type-tilordning

La oss forestille oss et system der en rekke enheter har en basisklasse som inneholder deres generelle egenskaper (navn, opprettelsesdato, eier, status osv.). Det er også et krav om å implementere et søk etter disse egenskapene og vise en liste over resultater. Kartlegging innebærer, igjen, kun å bruke grunnleggende egenskaper.

Med tanke på modellfleksibilitet er Table Per Type mapping godt egnet for denne oppgaven, hvor det lages en egen tabell for hver type. For eksempel har vi en basisklasse Kjøretøy og etterkommere - Personbil, Lastebil, Motorsykkel. I dette tilfellet vil det bli opprettet fire tabeller i databasen.

La oss skrive et søk som leser søkeresultater basert på et kriterium. For eksempel er datoen lagt til ikke tidligere enn 10 dager siden:

Var kjøretøy = kontekst.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .ToList();
Og la oss se hva Entity Framework konverterer det til:

VELG CASE NÅR ((IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1) OG (. ER IKKE NULL)))) OG (IKKE ((. = 1) OG (. . ER IKKE NULL)))) SÅ "0X" NÅR ((. = 1) OG (. ER IKKE NULL)) SÅ "0X0X" NÅR ((. = 1) OG (. ER IKKE NULL)) SÅ "0X1X" ELSE "0X2X" END AS , . SOM, . SOM, . SOM , EKSEMPEL NÅR ((IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1)) OG (. ER IKKE NULL)))) SÅ CAST(NULL SOM bit) NÅR ((. = 1) OG (. ER IKKE NULL)) SÅ . NÅR ((. = 1) OG (. ER IKKE NULL)) SÅ CAST(NULL SOM bit) ENDER SOM , KAPITEL NÅR ((IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE (( . = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1) OG (. ER IKKE NULL)))) SÅ CAST(NULL AS int) NÅR ((. = 1) OG (. ER NULL) IKKE NULL)) SÅ CAST(NULL AS int) NÅR ((. = 1) OG (. ER IKKE NULL)) SÅ . SLUT SOM , KAPITEL NÅR ((IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1) OG (. ER IKKE NULL))) OG (IKKE ((. = 1)) OG (. ER IKKE NULL)))) THEN CAST(NULL AS int) WHEN ((. = 1) AND (. IS NOT NULL)) THEN CAST(NULL AS int) NÅR ((. = 1) AND (. IS) IKKE NULL)) SÅ CAST(NULL AS int) ELSE . SLUTT FRA . SOM VENSTRE YTRE JOIN (VELG . AS , . AS , cast(1 as bit) FROM . AS ) SOM PÅ . = . VENSTRE YTRE JOIN (VELG . AS , . AS , cast (1 som bit) SOM FRA . AS ) SOM PÅ . = . VENSTRE YTRE JOIN (VELG . AS , . AS , cast (1 som bit) SOM FRA . AS ) SOM PÅ . = . HVOR. >
Det viser seg at vi bare trenger grunnleggende informasjon, og Entity Framework leser alt, og med en ganske tungvint forespørsel. Det er faktisk ikke noe galt med denne spesielle situasjonen - selv om vi velger objekter fra en samling av basisklasser, må rammeverket respektere polymorf atferd og returnere et objekt av typen det ble opprettet med.

Hovedspørsmålet her er hvordan man forenkler spørringen slik at den ikke leser unødvendige ting? Heldigvis, fra og med Entity Framework 5, er det en slik mulighet - dette er bruken av projeksjon. Vi lager ganske enkelt et objekt av en annen type eller et anonymt, og bruker bare egenskapene til basisenheten for å fylle det:

Var kjøretøy = kontekst.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .Select(v => new (Id = v.Id, CreatedAt = v .CreatedAt, Name = v.Name )).ToList();
Og alt blir mye enklere:

VELG 1 SOM , . SOM, . SOM, . SOM FRA. SOM HVOR. >= (DATEADD (dag, -10, SysUtcDateTime()))
Men det er også dårlige nyheter - hvis det er en samling i basisklassen, og den må leses, gjenstår problemet. Her er et eksempel:

Var kjøretøy = kontekst.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)) .Select(v => new (Id = v.Id, CreatedAt = v .CreatedAt, Name = v.Name, ServiceTickets = v.ServiceTickets )).ToList();
Og SQL generert for det:

PLUKKE UT. SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM, . SOM FRA (VELG . AS , . AS , . AS , . AS , . AS , . AS , 1 AS , . AS , . AS , . AS , CASE NÅR (. IS NULL) THEN CAST(NULL AS int) ELSE 1 SLUTT FRA . SOM VENSTRE YTRE SKJØTING . SOM PÅ . = . VENSTRE YTRE SKJØTING . SOM PÅ . = . VENSTRE YTRE SKJØTING . SOM PÅ . = . VENSTRE YTRE SKJØTING . SOM PÅ . = . HVOR . >= (DATOADT (dag) -10, SysUtcDateTime()))) SOM BESTILLING AV . ASC,. ASC,. ASC,. ASC,. A.S.C.
Jeg opprettet en billett for Entity Framework om dette emnet: https://entityframework.codeplex.com/workitem/2814, men de fortalte meg høflig at på grunn av den store kompleksiteten og faren for å bryte alt, ville de ikke fikse det.

I noen tilfeller, når størrelsen på basen og/eller antall arveobjekter er liten, kan du leve med dette. Hvis slike forespørsler begynner å redusere ytelsen betydelig, må du se etter løsninger. Siden problemet ikke kan forhindres på selve rammeverkets nivå, er det nødvendig med en løsning. Det enkleste alternativet her er å fullføre lesingen av samlingene med separate søk, for eksempel:

//Opprett en grunnleggende spørring var vehiclesQuery = context.Vehicles .AsNoTracking() .Where(v => v.CreatedAt >= DbFunctions.AddDays(DateTime.UtcNow, -10)); //Les objekter ved hjelp av projeksjon på hjelpeklassen, og ignorer samlinger var vehicles = vehiclesQuery .Select(v => new VehicleDto ( Id = v.Id, CreatedAt = v.CreatedAt, Name = v.Name )) .ToList(); //Fullfør samlingselementene relatert til alle objektene som ble returnert av den opprinnelige forespørselen var serviceTickets = context.ServiceTickets .AsNoTracking() .Where(s => vehiclesQuery.Any(v => v.Id == s.VehicleId)) .Ramse opp (); //Arranger elementene i de tilsvarende objektene vehicles.ForEach(v => v.ServiceTickets .AddRange(serviceTickets.Where(s => s.VehicleId == v.Id)));
Det er ingen universell oppskrift her, og løsningen ovenfor fungerer kanskje ikke i alle tilfeller. For eksempel kan en grunnleggende spørring være ganske kompleks, og å kjøre den på nytt for hver samling vil være dyr. Du kan prøve å omgå dette problemet ved å hente en liste over identifikatorer fra resultatene av basisspørringen, og deretter bruke den i alle videre underspørringer. Men hvis det er mange resultater, kan det hende at det ikke blir en seier. Dessuten, i dette tilfellet, bør du huske hva som ble sagt tidligere om Contains-metoden, som tydelig foreslår seg selv for å søke etter identifikatorer.

Jeg vil formulere den generelle tilnærmingen til å løse problemet som følger: hvis det er mulig å ikke bruke Table Per Type-kartlegging, er det bedre å ikke bruke det. I tilfeller hvor det er vanskelig å klare seg uten det, må du prøve alternativene beskrevet ovenfor og se om de gir deg en gevinst.

Tilleggsinformasjon

Ytelsesrelaterte nyanser som du bør være oppmerksom på når du arbeider med Entity Framework (inkludert de som er beskrevet i artikkelen) er kort beskrevet på denne lenken: https://msdn.microsoft.com/en-us/data/hh949853.aspx . Dessverre er ikke alle problemer angitt alternative løsninger, men informasjonen er fortsatt veldig nyttig. Det skal også bemerkes at i det minste punkt 4.3 ikke er bekreftet i praksis for Entity Framework 6.1.3.

Du kan hjelpe og overføre noen midler til utviklingen av nettstedet