Zelfstudie: Meer informatie over geavanceerde scenario's- ASP.NET MVC met EF Core
In de vorige zelfstudie hebt u overname van tabellen per hiërarchie geïmplementeerd. In deze zelfstudie worden verschillende onderwerpen geïntroduceerd die handig zijn om rekening mee te houden wanneer u verder gaat dan de basisprincipes van het ontwikkelen van ASP.NET Core-webtoepassingen die gebruikmaken van Entity Framework Core.
In deze zelfstudie gaat u het volgende doen:
- Onbewerkte SQL-query's uitvoeren
- Een query aanroepen om entiteiten te retourneren
- Een query aanroepen om andere typen te retourneren
- Een bijwerkquery aanroepen
- SQL-queries onderzoeken
- Een abstractielaag maken
- Meer informatie over automatische wijzigingsdetectie
- Meer informatie over EF Core broncode en ontwikkelingsplannen
- Meer informatie over het gebruik van dynamische LINQ om code te vereenvoudigen
Voorwaarden
Onbewerkte SQL-query's uitvoeren
Een van de voordelen van het gebruik van Entity Framework is dat uw code niet te nauw wordt gekoppeld aan een bepaalde methode voor het opslaan van gegevens. Dit doet u door SQL-query's en -opdrachten voor u te genereren, zodat u ze zelf niet hoeft te schrijven. Er zijn echter uitzonderlijke scenario's waarin u specifieke SQL-query's moet uitvoeren die u handmatig hebt gemaakt. Voor deze scenario's bevat de First API van Entity Framework Code First methoden waarmee u SQL-opdrachten rechtstreeks aan de database kunt doorgeven. U hebt de volgende opties in EF Core 1.0:
Gebruik de methode
DbSet.FromSql
voor query's die entiteitstypen retourneren. De geretourneerde objecten moeten van het type zijn dat wordt verwacht door hetDbSet
-object en ze worden automatisch bijgehouden door de databasecontext, tenzij u het bijhouden van uitschakelt.Gebruik de
Database.ExecuteSqlCommand
voor opdrachten die geen query uitvoeren.
Als u een query wilt uitvoeren die typen retourneert die geen entiteiten zijn, kunt u ADO.NET gebruiken met de databaseverbinding van EF. De geretourneerde gegevens worden niet bijgehouden door de databasecontext, zelfs niet als u deze methode gebruikt om entiteitstypen op te halen.
Zoals altijd geldt wanneer u SQL-opdrachten uitvoert in een webtoepassing, moet u voorzorgsmaatregelen nemen om uw site te beschermen tegen SQL-injectieaanvallen. Een manier om dit te doen, is het gebruik van geparameteriseerde query's om ervoor te zorgen dat tekenreeksen die door een webpagina worden verzonden, niet als SQL-opdrachten kunnen worden geïnterpreteerd. In deze zelfstudie gebruikt u geparameteriseerde query's bij het integreren van gebruikersinvoer in een query.
Een query aanroepen om entiteiten te retourneren
De DbSet<TEntity>
-klasse biedt een methode die u kunt gebruiken om een query uit te voeren waarmee een entiteit van het type TEntity
wordt geretourneerd. Als u wilt zien hoe dit werkt, wijzigt u de code in de Details
methode van de afdelingscontroller.
Vervang in DepartmentsController.cs
in de methode Details
de code waarmee een afdeling wordt opgehaald door een aanroep van de FromSql
methode, zoals te zien is in de volgende gemarkeerde code:
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);
}
Als u wilt controleren of de nieuwe code correct werkt, selecteert u het tabblad Afdelingen en Details voor een van de afdelingen.
Een query aanroepen om andere typen te retourneren
Eerder hebt u een raster met studentstatistieken gemaakt voor de Over-pagina, met het aantal studenten voor elke inschrijvingsdatum. U hebt de gegevens opgehaald uit de entiteitsset Students (_context.Students
) en LINQ gebruikt om de resultaten te projecteren in een lijst van EnrollmentDateGroup
view model objecten. Stel dat u de SQL zelf wilt schrijven in plaats van LINQ te gebruiken. Hiervoor moet u een SQL-query uitvoeren die iets anders retourneert dan entiteitsobjecten. In EF Core 1.0 kunt u dat doen door ADO.NET code te schrijven en de databaseverbinding van EF op te halen.
Vervang in HomeController.cs
de About
methode door de volgende code:
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);
}
Een using-instructie toevoegen:
using System.Data.Common;
Voer de app uit en ga naar de pagina Info. Er worden dezelfde gegevens weergegeven als voorheen.
Een bijwerkquery aanroepen
Stel dat de beheerders van Contoso University algemene wijzigingen in de database willen uitvoeren, zoals het wijzigen van het aantal studiepunten voor elke cursus. Als de universiteit een groot aantal cursussen heeft, zou het inefficiënt zijn om ze allemaal op te halen als entiteiten en ze afzonderlijk te wijzigen. In deze sectie implementeert u een webpagina waarmee de gebruiker een factor kan opgeven waarmee het aantal tegoeden voor alle cursussen kan worden gewijzigd en u de wijziging aanbrengt door een SQL UPDATE-instructie uit te voeren. De webpagina ziet er als volgt uit:
Voeg in CoursesController.cs
de methoden UpdateCourseCredits toe voor HttpGet en 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();
}
Wanneer de controller een HttpGet-aanvraag verwerkt, wordt er niets geretourneerd in ViewData["RowsAffected"]
en wordt in de weergave een leeg tekstvak en een verzendknop weergegeven, zoals wordt weergegeven in de vorige afbeelding.
Wanneer op de knop Bijwerken wordt geklikt, wordt de HttpPost-methode aangeroepen en heeft vermenigvuldiger de waarde die in het tekstvak is ingevoerd. De code voert vervolgens de SQL uit die cursussen bijwerkt en het aantal betrokken rijen retourneert naar de weergave in ViewData
. Wanneer de weergave een RowsAffected
waarde krijgt, wordt het aantal rijen weergegeven dat is bijgewerkt.
Klik in Solution Explorer-met de rechtermuisknop op de map Weergaven/Cursussen en klik vervolgens op Toevoegen > Nieuw item.
Klik in het dialoogvenster Nieuw item toevoegen op ASP.NET Core onder Geïnstalleerd in het linkerdeelvenster, klik op Razor Weergaveen geef de nieuwe weergave een naam UpdateCourseCredits.cshtml
.
Vervang in Views/Courses/UpdateCourseCredits.cshtml
de sjablooncode door de volgende code:
@{
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>
Voer de methode UpdateCourseCredits
uit door het tabblad Courses te selecteren en vervolgens '/UpdateCourseCredits' toe te voegen aan het einde van de URL in de adresbalk van de browser (bijvoorbeeld: http://localhost:5813/Courses/UpdateCourseCredits
). Voer een getal in het tekstvak in:
Klik op bijwerken. U ziet het aantal rijen dat is beïnvloed:
Klik op Terug naar lijst om de lijst met cursussen met het herziene aantal studiepunten weer te geven.
Houd er rekening mee dat productiecode ervoor zorgt dat updates altijd geldige gegevens opleveren. De vereenvoudigde code die hier wordt weergegeven, kan het aantal tegoeden vermenigvuldigen dat voldoende is om getallen groter dan 5 te geven. (De eigenschap Credits
heeft een [Range(0, 5)]
kenmerk.) De updatequery werkt wel, maar de ongeldige gegevens kunnen onverwachte resultaten veroorzaken in andere onderdelen van het systeem waarvan wordt uitgegaan dat het aantal tegoeden 5 of minder is.
Voor meer informatie over onbewerkte SQL-query's, zie Onbewerkte SQL-query's.
SQL-query's onderzoeken
Soms is het handig om de werkelijke SQL-query's te zien die naar de database worden verzonden. Ingebouwde functionaliteit voor logboekregistratie voor ASP.NET Core wordt automatisch gebruikt door EF Core om logboeken te schrijven die de SQL voor query's en updates bevatten. In deze sectie ziet u enkele voorbeelden van SQL-logboekregistratie.
Open StudentsController.cs
en stel in de methode Details
een onderbrekingspunt in op de instructie if (student == null)
.
Voer de app uit in de foutopsporingsmodus en ga naar de pagina Details voor een leerling/student.
Ga naar het venster Uitvoer met foutopsporingsuitvoer en u ziet de query:
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]
U ziet hier iets dat u misschien verrast: de SQL selecteert maximaal 2 rijen (TOP(2)
) uit de tabel Person. De methode SingleOrDefaultAsync
wordt niet omgezet in één rij op de server. Dit is de reden waarom:
- Als de query meerdere rijen retourneert, retourneert de methode null.
- Om te bepalen of de query meerdere rijen retourneert, moet EF controleren of deze ten minste 2 retourneert.
U hoeft de foutopsporingsmodus niet te gebruiken en stop bij een onderbrekingspunt om logboekuitvoer op te halen in het venster Uitvoer. Het is gewoon een handige manier om de logboekregistratie te stoppen op het moment dat u de uitvoer wilt bekijken. Als u dat niet doet, blijft de logboekregistratie doorgaan en moet u terugbladeren om de delen te vinden waarin u geïnteresseerd bent.
Een abstractielaag maken
Veel ontwikkelaars schrijven code om de repository- en unit of work-patronen te implementeren als een wrapper rond code die werkt met Entity Framework. Deze patronen zijn bedoeld om een abstractielaag te maken tussen de gegevenstoegangslaag en de bedrijfslogicalaag van een toepassing. Het implementeren van deze patronen kan helpen uw toepassing te isoleren van wijzigingen in het gegevensarchief en kan geautomatiseerde eenheidstests of testgestuurde ontwikkeling (TDD) vergemakkelijken. Het schrijven van aanvullende code voor het implementeren van deze patronen is echter niet altijd de beste keuze voor toepassingen die EF gebruiken, om verschillende redenen:
De EF-contextklasse zelf isoleert uw code van gegevensopslagspecifieke code.
De EF-contextklasse kan fungeren als een eenheid van werkklasse voor database-updates die u met EF doet.
EF bevat functies voor het implementeren van TDD zonder opslagplaatscode te schrijven.
Voor informatie over het implementeren van de repository en werkonderdelenpatronen, zie de Entity Framework 5-versie van deze zelfstudiereeks.
Entity Framework Core implementeert een in-memory databaseprovider die kan worden gebruikt voor testen. Zie Testen met InMemoryvoor meer informatie.
Automatische wijzigingsdetectie
Het Entity Framework bepaalt hoe een entiteit is gewijzigd (en daarom welke updates naar de database moeten worden verzonden) door de huidige waarden van een entiteit te vergelijken met de oorspronkelijke waarden. De oorspronkelijke waarden worden opgeslagen wanneer de entiteit wordt opgevraagd of gekoppeld. Enkele van de methoden die automatische wijzigingsdetectie veroorzaken, zijn het volgende:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Invoerregels
Als u een groot aantal entiteiten bijhoudt en u een van deze methoden vaak in een lus aanroept, krijgt u mogelijk aanzienlijke prestatieverbeteringen door de automatische wijzigingsdetectie tijdelijk uit te schakelen met behulp van de eigenschap ChangeTracker.AutoDetectChangesEnabled
. Bijvoorbeeld:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
EF Core broncode en ontwikkelingsplannen
De Entity Framework Core-bron bevindt zich op https://github.com/dotnet/efcore. De EF Core-opslagplaats bevat nachtelijke versies, probleemtracking, functiespecificaties, notities van ontwerpvergaderingen en de roadmap voor toekomstige ontwikkeling. U kunt fouten opslaan of vinden en bijdragen.
Hoewel de broncode open is, wordt Entity Framework Core volledig ondersteund als een Microsoft-product. Het Microsoft Entity Framework-team houdt controle over welke bijdragen worden geaccepteerd en test alle codewijzigingen om de kwaliteit van elke release te garanderen.
Reverse-engineering van bestaande database
Als u een gegevensmodel met inbegrip van entiteitsklassen uit een bestaande database wilt reverse-engineeren, gebruikt u de opdracht scaffold-dbcontext. Zie de zelfstudie om aan de slag te gaan .
Dynamische LINQ gebruiken om code te vereenvoudigen
De derde zelfstudie in deze reeks laat zien hoe u LINQ-code schrijft door kolomnamen in een switch
instructie vast te coderen. Met twee kolommen waaruit u kunt kiezen werkt dit prima, maar als u veel kolommen hebt, kan de code omslachtig worden. U kunt dit probleem oplossen door de methode EF.Property
te gebruiken om de naam van de eigenschap op te geven als een tekenreeks. Als u deze methode wilt uitproberen, vervangt u de Index
methode in de StudentsController
door de volgende code.
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));
}
Bevestigingen
Tom Dykstra en Rick Anderson (Twitter: @RickAndMSFT)) schreven deze tutorial. Rowan Miller, Diego Vega en andere leden van het Entity Framework-team hielpen met codebeoordelingen en hielpen bij het opsporen van problemen die ontstonden tijdens het schrijven van code voor de zelfstudies. John Parente en Paul Goldman werkten aan het bijwerken van de zelfstudie voor ASP.NET Core 2.2.
Veelvoorkomende fouten oplossen
ContosoUniversity.dll gebruikt door een ander proces
Foutmelding:
Kan niet openen... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' omdat deze door een ander proces wordt gebruikt.
Oplossing:
Stop de site in IIS Express. Ga naar het Systeemvak van Windows, zoek IIS Express en klik met de rechtermuisknop op het bijbehorende pictogram, selecteer de site Contoso University en klik vervolgens op Site stoppen.
Migratie gestructureerd zonder code in de methoden Op en Neer
Mogelijke oorzaak:
De EF CLI-opdrachten sluiten en slaan codebestanden niet automatisch op. Als u niet-opgeslagen wijzigingen hebt wanneer u de opdracht migrations add
uitvoert, worden uw wijzigingen niet gevonden door EF.
Oplossing:
Voer de opdracht migrations remove
uit, sla de codewijzigingen op en voer de opdracht migrations add
opnieuw uit.
Fouten tijdens het uitvoeren van database-update
Het is mogelijk om andere fouten op te halen bij het aanbrengen van schemawijzigingen in een database met bestaande gegevens. Als u migratiefouten krijgt die u niet kunt oplossen, kunt u de databasenaam in de verbindingsreeks wijzigen of de database verwijderen. Met een nieuwe database zijn er geen gegevens die moeten worden gemigreerd en de opdracht database bijwerken is veel waarschijnlijker zonder fouten voltooid.
De eenvoudigste methode is om de naam van de database in appsettings.json
te wijzigen. De volgende keer dat u database update
uitvoert, wordt er een nieuwe database gemaakt.
Als u een database in SSOX wilt verwijderen, klikt u met de rechtermuisknop op de database, klikt u op Verwijderenen selecteert u in het dialoogvenster Database verwijderenBestaande verbindingen sluiten en klikt u op OK-.
Als u een database wilt verwijderen met behulp van de CLI, voert u de database drop
CLI-opdracht uit:
dotnet ef database drop
Fout bij het vinden van een SQL Server-exemplaar
Foutmelding:
Er is een netwerkgerelateerde of exemplaarspecifieke fout opgetreden tijdens het tot stand brengen van een verbinding met SQL Server. De server is niet gevonden of is niet toegankelijk. Controleer of de naam van het exemplaar juist is en of SQL Server is geconfigureerd om externe verbindingen toe te staan. (provider: SQL-netwerkinterfaces, fout: 26 - Fout bij het zoeken naar opgegeven server/instantie)
Oplossing:
Controleer de verbindingsreeks. Als u het databasebestand handmatig hebt verwijderd, wijzigt u de naam van de database in de constructietekenreeks om opnieuw te beginnen met een nieuwe database.
De code ophalen
de voltooide toepassing downloaden of weergeven.
Aanvullende informatiebronnen
Zie de Entity Framework Core-documentatievoor meer informatie over EF Core. Er is ook een boek beschikbaar: Entity Framework Core in Action.
Zie Host en implementeer ASP.NET Corevoor meer informatie over het implementeren van een web-app.
Zie Overzicht van ASP.NET Corevoor meer informatie over andere onderwerpen met betrekking tot ASP.NET Core MVC, zoals verificatie en autorisatie.
Zie Enterprise-web-app-patronenvoor hulp bij het maken van een betrouwbare, veilige, uitvoerbare, testbare en schaalbare ASP.NET Core-app. Er is een volledige voorbeeldweb-app van productiekwaliteit beschikbaar waarmee de patronen worden geïmplementeerd.
Volgende stappen
In deze zelfstudie gaat u het volgende doen:
- Onbewerkte SQL-query's uitgevoerd
- Een query uitgevoerd om entiteiten te retourneren.
- Een query uitgevoerd om andere typen te retourneren
- Wordt een bijwerkquery genoemd
- Onderzochte SQL-query's
- Een abstractielaag gemaakt
- Meer informatie over automatische wijzigingsdetectie
- Meer informatie over EF Core broncode en ontwikkelingsplannen
- Geleerd hoe u dynamische LINQ gebruikt om code te vereenvoudigen
Hiermee voltooit u deze reeks zelfstudies over het gebruik van Entity Framework Core in een ASP.NET Core MVC-toepassing. Deze reeks werkte met een nieuwe database; Een alternatief is het reverse-engineeren van een model uit een bestaande database.