Självstudie: Lär dig mer om avancerade scenarier – ASP.NET MVC med EF Core
I den föregående handledningen implementerade du tabell-per-hierarki-arv. I den här självstudien beskrivs flera ämnen som är användbara att känna till när du går längre än grunderna i att utveckla ASP.NET Core-webbprogram som använder Entity Framework Core.
I den här handledningen:
- Utföra råa SQL-frågor
- Anropa en fråga för att returnera entiteter
- Anropa en sökfråga för att returnera andra typer
- Anropa en uppdateringsfråga
- Granska SQL-frågor
- Skapa ett abstraktionslager
- Läs mer om automatisk ändringsidentifiering
- Lär dig mer om EF Core källkods- och utvecklingsplaner
- Lär dig hur du använder dynamisk LINQ för att förenkla kod
Förutsättningar
Utföra råa SQL-frågor
En av fördelarna med att använda Entity Framework är att den undviker att binda koden för nära en viss metod för att lagra data. Det gör det genom att generera SQL-frågor och kommandon åt dig, vilket också gör att du slipper skriva dem själv. Men det finns exceptionella scenarier när du behöver köra specifika SQL-frågor som du har skapat manuellt. I dessa scenarier innehåller Entity Framework Code First-API:et metoder som gör att du kan skicka SQL-kommandon direkt till databasen. Du har följande alternativ i EF Core 1.0:
Använd metoden
DbSet.FromSql
för frågor som returnerar entitetstyper. De returnerade objekten måste vara av den typ som förväntas avDbSet
-objektet, och de spåras automatiskt av databaskontexten såvida du inte har inaktiverat spårning .Använd
Database.ExecuteSqlCommand
för kommandon som inte är frågekommandon.
Om du behöver köra en fråga som returnerar typer som inte är entiteter kan du använda ADO.NET med databasanslutningen som tillhandahålls av EF. Returnerade data spåras inte av databaskontexten, även om du använder den här metoden för att hämta entitetstyper.
Som alltid när du kör SQL-kommandon i ett webbprogram måste du vidta försiktighetsåtgärder för att skydda din webbplats mot SQL-inmatningsattacker. Ett sätt att göra det är att använda parametriserade frågor för att se till att strängar som skickas av en webbsida inte kan tolkas som SQL-kommandon. I den här handledningen använder du parametriserade frågor när du integrerar användardata i en fråga.
Kör en sökfråga för att returnera entiteter
Klassen DbSet<TEntity>
innehåller en metod som du kan använda för att köra en fråga som returnerar en entitet av typen TEntity
. Om du vill se hur detta fungerar ändrar du koden i Details
-metoden för avdelningskontrollanten.
I DepartmentsController.cs
, i metoden Details
, ersätt koden som hämtar en avdelning med ett anrop till metoden FromSql
, som visas i följande markerade kod:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
Kontrollera att den nya koden fungerar korrekt genom att välja fliken Avdelningar och sedan Information för någon av avdelningarna.
Anropa en sökfråga för att returnera andra typer
Tidigare skapade du ett rutnät för elevstatistik för sidan Om som visade antalet studenter för varje registreringsdatum. Du fick data från entitetsuppsättningen Studenter (_context.Students
) och använde LINQ för att projicera resultatet till en lista över EnrollmentDateGroup
vy-modellobjekt. Anta att du vill skriva själva SQL i stället för att använda LINQ. För att göra det måste du köra en SQL-fråga som returnerar något annat än entitetsobjekt. I EF Core 1.0 är ett sätt att göra det att skriva ADO.NET kod och hämta databasanslutningen från EF.
I HomeController.cs
ersätter du metoden About
med följande kod:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
Lägg till en using-instruktion:
using System.Data.Common;
Kör appen och gå till Om-sidan. Den visar samma data som tidigare.
Anropa en uppdateringsfråga
Anta att Contoso University-administratörer vill utföra globala ändringar i databasen, till exempel ändra antalet krediter för varje kurs. Om universitetet har ett stort antal kurser skulle det vara ineffektivt att hämta dem alla som entiteter och ändra dem individuellt. I det här avsnittet implementerar du en webbsida som gör det möjligt för användaren att ange en faktor som du kan använda för att ändra antalet krediter för alla kurser, så gör du ändringen genom att köra en SQL UPDATE-instruktion. Webbsidan ser ut som följande bild:
I CoursesController.cs
lägger du till UpdateCourseCredits-metoder för HttpGet och HttpPost:
public IActionResult UpdateCourseCredits()
{
return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
När kontrollanten bearbetar en HttpGet-begäran returneras ingenting i ViewData["RowsAffected"]
, och vyn visar en tom textruta och en skicka-knapp, enligt föregående bild.
När knappen Uppdatera klickas anropas metoden HttpPost och multiplikatorn har värdet som anges i textrutan. Koden kör sedan den SQL som uppdaterar kurser och returnerar antalet berörda rader till vyn i ViewData
. När vyn får ett RowsAffected
värde visas antalet rader som har uppdaterats.
Högerklicka på mappen Views/Courses i Solution Exploreroch klicka sedan på Lägg till > nytt objekt.
I dialogrutan Lägg till nytt objekt klickar du på ASP.NET Core under Installerat i det vänstra fönstret, klickar på Razor Visaoch ger den nya vyn namnet UpdateCourseCredits.cshtml
.
I Views/Courses/UpdateCourseCredits.cshtml
ersätter du mallkoden med följande kod:
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
@if (ViewData["RowsAffected"] == null)
{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Kör metoden UpdateCourseCredits
genom att välja fliken Kurser och sedan lägga till "/UpdateCourseCredits" i slutet av URL:en i webbläsarens adressfält (till exempel: http://localhost:5813/Courses/UpdateCourseCredits
). Ange ett tal i textrutan:
Klicka på Uppdatera. Du ser hur många rader som påverkas:
Klicka på Tillbaka till lista för att se listan över kurser med det reviderade antalet krediter.
Observera att produktionskoden säkerställer att uppdateringar alltid resulterar i giltiga data. Den förenklade koden som visas här kan multiplicera antalet krediter tillräckligt för att resultera i tal större än 5. (Egenskapen Credits
har ett [Range(0, 5)]
-attribut.) Uppdateringsfrågan skulle fungera, men ogiltiga data kan orsaka oväntade resultat i andra delar av systemet som förutsätter att antalet krediter är 5 eller mindre.
Mer information om råa SQL-frågor finns i Råa SQL-frågor.
Granska SQL-frågor
Ibland är det bra att kunna se de faktiska SQL-frågor som skickas till databasen. Inbyggda loggningsfunktioner för ASP.NET Core används automatiskt av EF Core för att skriva loggar som innehåller SQL för frågor och uppdateringar. I det här avsnittet visas några exempel på SQL-loggning.
Öppna StudentsController.cs
och i metoden Details
anger du en brytpunkt för if (student == null)
-instruktionen.
Kör appen i felsökningsläge och gå till sidan Information för en elev.
Gå till Output-fönstret som visar felsökningsutdata och där ser du frågan:
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]
Du kommer att märka något här som kan överraska dig: SQL väljer upp till 2 rader (TOP(2)
) från tabellen Person. Metoden SingleOrDefaultAsync
resulterar inte i 1 rad på servern. Här är varför:
- Om frågan skulle returnera flera rader returnerar metoden null.
- För att avgöra om frågan skulle returnera flera rader måste EF kontrollera om den returnerar minst 2.
Observera att du inte behöver använda felsökningsläge och stoppa vid en brytpunkt för att få loggningsutdata i fönstret Utdata. Det är bara ett praktiskt sätt att stoppa loggningen vid den punkt där du vill titta på utdata. Om du inte gör det fortsätter loggningen och du måste rulla tillbaka för att hitta de delar som du är intresserad av.
Skapa ett abstraktionslager
Många utvecklare skriver kod för att implementera mönstren för lagring och arbetsenhet som ett omslag runt kod som fungerar med Entity Framework. Dessa mönster är avsedda att skapa ett abstraktionslager mellan dataåtkomstskiktet och affärslogikskiktet i ett program. Genom att implementera dessa mönster kan du isolera ditt program från ändringar i datalagret och underlätta automatiserad enhetstestning eller testdriven utveckling (TDD). Att skriva ytterligare kod för att implementera dessa mönster är dock inte alltid det bästa valet för program som använder EF, av flera skäl:
SJÄLVA EF-kontextklassen isolerar koden från datalagringsspecifik kod.
EF-kontextklassen kan fungera som en "unit of work"-klass för databasuppdateringar som du utför med EF.
EF innehåller funktioner för att implementera TDD utan att skriva lagringsplatskod.
Information om hur du implementerar lager- och enhetsmönstren finns i Entity Framework 5-versionen av denna handledningsserie.
Entity Framework Core implementerar en minnesintern databasprovider som kan användas för testning. Mer information finns i Test med InMemory.
Automatisk ändringsidentifiering
Entity Framework avgör hur en entitet har ändrats (och därför vilka uppdateringar som måste skickas till databasen) genom att jämföra aktuella värden för en entitet med de ursprungliga värdena. De ursprungliga värdena lagras när entiteten efterfrågas eller kopplas. Några av de metoder som orsakar automatisk ändringsidentifiering är följande:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
Om du spårar ett stort antal entiteter och du anropar någon av dessa metoder många gånger i en loop kan du få betydande prestandaförbättringar genom att tillfälligt inaktivera automatisk ändringsidentifiering med hjälp av egenskapen ChangeTracker.AutoDetectChangesEnabled
. Till exempel:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
EF Core källkods- och utvecklingsplaner
Entity Framework Core-källan finns på https://github.com/dotnet/efcore. EF Core-lagringsplatsen innehåller nattliga byggen, problemspårning, funktionsspecifikationer, designmötesanteckningar och färdplanen för framtida utvecklingsplaner. Du kan fila eller hitta buggar och bidra.
Även om källkoden är öppen stöds Entity Framework Core fullt ut som en Microsoft-produkt. Microsoft Entity Framework-teamet har kontroll över vilka bidrag som godkänns och testar alla kodändringar för att säkerställa kvaliteten på varje version.
Bakåtutveckla i befintlig databas
Om du vill bakåtkompilera en datamodell med entitetsklasser från en befintlig databas använder du kommandot scaffold-dbcontext. Se till komma igång-handledningen .
Använda dynamisk LINQ för att förenkla koden
Den tredje handledningen i den här serien visar hur du skriver LINQ-kod genom att hårdkoda kolumnnamn i en switch
-instruktion. Med två kolumner att välja mellan fungerar detta bra, men om du har många kolumner kan koden bli utförlig. För att lösa problemet kan du använda metoden EF.Property
för att ange namnet på egenskapen som en sträng. Om du vill prova den här metoden ersätter du metoden Index
i StudentsController
med följande kod.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}
Erkännanden
Tom Dykstra och Rick Anderson (Twitter @RickAndMSFT)) skrev den här handledningen. Rowan Miller, Diego Vega och andra medlemmar i Entity Framework-teamet hjälpte till med kodgranskningar och hjälpte till att felsöka problem som uppstod när vi skrev kod för självstudierna. John Parente och Paul Goldman arbetade med att uppdatera handledningen för ASP.NET Core 2.2.
Felsöka vanliga fel
ContosoUniversity.dll som används av en annan process
Felmeddelande:
Det går inte att öppna ... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dlleftersom den används av en annan process.
Lösning:
Stoppa webbplatsen i IIS Express. Gå till Windows-systemfältet, leta upp IIS Express och högerklicka på dess ikon, välj Contoso University-webbplatsen och klicka sedan på Stoppa webbplats.
Migrering stöds utan kodning i metoderna för upp och ner
Möjlig orsak:
EF CLI-kommandona stänger och sparar inte kodfiler automatiskt. Om du har ändringar som inte har sparats när du kör kommandot migrations add
hittar EF inte dina ändringar.
Lösning:
Kör kommandot migrations remove
, spara kodändringarna och kör kommandot migrations add
igen.
Fel vid körning av databasuppdatering
Det går att få andra fel när du gör schemaändringar i en databas som har befintliga data. Om du får migreringsfel som du inte kan lösa kan du antingen ändra databasnamnet i anslutningssträngen eller ta bort databasen. Med en ny databas finns det inga data att migrera och kommandot update-database är mycket mer sannolikt att slutföras utan fel.
Den enklaste metoden är att byta namn på databasen i appsettings.json
. Nästa gång du kör database update
skapas en ny databas.
Om du vill ta bort en databas i SSOX högerklickar du på databasen, klickar på Ta bortoch i dialogrutan Ta bort databas väljer du sedan Stäng befintliga anslutningar och klickar på OK.
Om du vill ta bort en databas med hjälp av CLI kör du kommandot database drop
CLI:
dotnet ef database drop
Fel vid lokalisering av SQL Server-instans
Felmeddelande:
Ett nätverksrelaterat eller instansspecifikt fel uppstod när en anslutning till SQL Server upprättades. Servern hittades inte eller var inte tillgänglig. Kontrollera att instansnamnet är korrekt och att SQL Server har konfigurerats för att tillåta fjärranslutningar. (providern: SQL Network Interfaces, fel: 26 – Fel vid lokalisering av angiven server/instans)
Lösning:
Kontrollera anslutningssträngen. Om du har tagit bort databasfilen manuellt ändrar du namnet på databasen i byggsträngen så att den börjar om med en ny databas.
Hämta koden
Ladda ned eller visa den slutförda ansökan.
Ytterligare resurser
Mer information om EF Corefinns i dokumentationen Entity Framework Core. En bok är också tillgänglig: Entity Framework Core in Action.
Information om hur du distribuerar en webbapp finns i Värd och distribuera ASP.NET Core.
Information om andra ämnen som rör ASP.NET Core MVC, till exempel autentisering och auktorisering, finns i Översikt över ASP.NET Core.
Vägledning om hur du skapar en tillförlitlig, säker, högpresterande, testbar och skalbar ASP.NET Core-app finns i Enterprise-webbappmönster. En komplett exempelwebbapp av produktionskvalitet som implementerar mönstren är tillgänglig.
Nästa steg
I den här handledningen:
- Utförde råa SQL-frågor
- Körande en sökfråga för att returnera entiteter
- Anropade en sökfråga för att returnera andra typer
- En uppdateringsfråga
- Undersökte SQL-frågor
- Skapade ett abstraktionslager
- Lärt sig om automatisk ändringsdetektering
- Har lärt dig mer om EF Core källkod och utvecklingsplaner
- Lärt dig hur du använder dynamisk LINQ för att förenkla kod
Detta slutför den här serien med självstudier om hur du använder Entity Framework Core i ett ASP.NET Core MVC-program. Den här serien använde en ny databas; ett alternativ är att bakåtkompilera en modell från en befintlig databas.
ASP.NET Core