Skriva C#LINQ-frågor för att fråga efter data
De flesta frågor i linq-dokumentationen (Introductory Language Integrated Query) skrivs med hjälp av linq-deklarativ frågesyntax. Frågesyntaxen måste dock översättas till metodanrop för .NET common language runtime (CLR) när koden kompileras. Dessa metodanrop anropar standardfrågeoperatorerna, som har namn som Where
, Select
, GroupBy
, Join
, Max
och Average
. Du kan anropa dem direkt med hjälp av metodsyntax i stället för frågesyntax.
Frågesyntax och metodsyntax är semantiskt identiska, men frågesyntaxen är ofta enklare och enklare att läsa. Vissa frågor måste uttryckas som metodanrop. Du måste till exempel använda ett metodanrop för att uttrycka en fråga som hämtar antalet element som matchar ett angivet villkor. Du måste också använda ett metodanrop för en fråga som hämtar elementet som har det maximala värdet i en källsekvens. Referensdokumentationen för standardfrågeoperatorerna i System.Linq namnområdet använder vanligtvis metodsyntax. Du bör bekanta dig med hur du använder metodsyntax i frågor och i själva frågeuttrycken.
Standardmetoder för frågeoperatortillägg
I följande exempel visas ett enkelt frågeuttryck och den semantiskt likvärdiga frågan som skrivits som en metodbaserad fråga.
int[] numbers = [ 5, 10, 8, 3, 6, 12 ];
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
foreach (int i in numQuery1)
{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}
Utdata från de två exemplen är identiska. Du kan se att frågevariabelns typ är samma i båda formulären: IEnumerable<T>.
För att förstå den metodbaserade frågan ska vi undersöka den närmare. Till höger om uttrycket ser du att where
satsen nu uttrycks som en instansmetod för numbers
objektet, som har en typ av IEnumerable<int>
. Om du är bekant med det allmänna IEnumerable<T> gränssnittet vet du att det inte har någon Where
metod. Men om du anropar intelliSense-slutförandelistan i Visual Studio IDE ser du inte bara en Where
metod, utan många andra metoder som Select
, SelectMany
, Join
och Orderby
. Dessa metoder implementerar standardfrågeoperatorerna.
Även om det ser ut som om IEnumerable<T> det innehåller fler metoder, så gör det inte det. Standardfrågeoperatorerna implementeras som tilläggsmetoder. Tilläggsmetoder "utökar" en befintlig typ. de kan anropas som om de vore instansmetoder för typen. Standardfrågeoperatorerna utökas IEnumerable<T> och därför kan du skriva numbers.Where(...)
.
Om du vill använda tilläggsmetoder tar du med dem i omfånget med using
direktiv. Från programmets synvinkel är en tilläggsmetod och en vanlig instansmetod densamma.
Mer information om tilläggsmetoder finns i Tilläggsmetoder. Mer information om vanliga frågeoperatorer finns i Översikt över vanliga frågeoperatorer (C#). Vissa LINQ-leverantörer, till exempel Entity Framework och LINQ till XML, implementerar sina egna standardfrågeoperatorer IEnumerable<T>och tilläggsmetoder för andra typer förutom .
Lambda-uttryck
I föregående exempel ser du att villkorsuttrycket (num % 2 == 0
) skickas som ett infogat argument till Enumerable.Where metoden: Where(num => num % 2 == 0).
Det här infogade uttrycket är ett lambda-uttryck. Det är ett bekvämt sätt att skriva kod som annars skulle behöva skrivas i mer besvärlig form. Till num
vänster om operatorn finns indatavariabeln som motsvarar num
i frågeuttrycket. Kompilatorn kan härleda typen av num
eftersom den vet att det numbers
är en allmän IEnumerable<T> typ. Brödtexten i lambda är precis samma som uttrycket i frågesyntaxen eller i något annat C#-uttryck eller -uttryck. Den kan innehålla metodanrop och annan komplex logik. Returvärdet är bara uttrycksresultatet. Vissa frågor kan bara uttryckas i metodsyntax och vissa av dessa kräver lambda-uttryck. Lambda-uttryck är ett kraftfullt och flexibelt verktyg i din LINQ-verktygslåda.
Sammansättning av frågor
I föregående kodexempel Enumerable.OrderBy anropas metoden med hjälp av punktoperatorn i anropet till Where
. Where
skapar en filtrerad sekvens och sorterar sedan Orderby
sekvensen som skapas av Where
. Eftersom frågor returnerar en IEnumerable
skriver du dem i metodsyntaxen genom att länka ihop metodanropen. Kompilatorn gör den här kompositionen när du skriver frågor med hjälp av frågesyntax. Eftersom en frågevariabel inte lagrar resultatet av frågan kan du ändra den eller använda den som grund för en ny fråga när som helst, även efter att du har kört den.
I följande exempel visas några enkla LINQ-frågor med hjälp av varje metod som angavs tidigare.
Kommentar
Dessa frågor fungerar på enkla minnesinterna samlingar. Den grundläggande syntaxen är dock identisk med den som används i LINQ till entiteter och LINQ till XML.
Exempel – frågesyntax
Du skriver de flesta frågor med frågesyntax för att skapa frågeuttryck. I följande exempel visas tre frågeuttryck. Det första frågeuttrycket visar hur du filtrerar eller begränsar resultat genom att tillämpa villkor med en where
sats. Den returnerar alla element i källsekvensen vars värden är större än 7 eller mindre än 3. Det andra uttrycket visar hur du beställer de returnerade resultaten. Det tredje uttrycket visar hur du grupperar resultat enligt en nyckel. Den här frågan returnerar två grupper baserat på ordets första bokstav.
List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
// The query variables can also be implicitly typed by using var
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num is < 3 or > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num is < 3 or > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
Typen av frågor är IEnumerable<T>. Alla dessa frågor kan skrivas med hjälp av var
det som visas i följande exempel:
var query = from num in numbers...
I varje tidigare exempel körs frågorna inte förrän du itererar över frågevariabeln i en instruktion eller annan foreach
instruktion.
Exempel – metodsyntax
Vissa frågeåtgärder måste uttryckas som ett metodanrop. De vanligaste metoderna är de metoder som returnerar numeriska singleton-värden, till exempel Sum, Max, Min, Averageoch så vidare. Dessa metoder måste alltid anropas sist i en fråga eftersom de returnerar ett enda värde och inte kan fungera som källa för en extra frågeåtgärd. I följande exempel visas ett metodanrop i ett frågeuttryck:
List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Om metoden har System.Action eller System.Func<TResult> parametrar tillhandahålls dessa argument i form av ett lambda-uttryck, som du ser i följande exempel:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
I föregående frågor körs endast fråga nr 4 omedelbart eftersom den returnerar ett enda värde och inte en allmän IEnumerable<T> samling. Själva metoden använder foreach
eller liknande kod för att beräkna dess värde.
Var och en av de tidigare frågorna kan skrivas med implicit inmatning med var
, som du ser i följande exempel:
// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);
Exempel – Syntax för blandade frågor och metoder
Det här exemplet visar hur du använder metodsyntax för resultatet av en frågesats. Omslut bara frågeuttrycket inom parenteser och tillämpa sedan punktoperatorn och anropa metoden. I följande exempel returnerar fråga 7 ett antal tal vars värde är mellan 3 och 7. I allmänhet är det dock bättre att använda en andra variabel för att lagra resultatet av metodanropet. På så sätt är det mindre troligt att frågan förväxlas med resultatet av frågan.
// Query #7.
// Using a query expression with method syntax
var numCount1 = (
from num in numbers1
where num is > 3 and < 7
select num
).Count();
// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num is > 3 and < 7
select num;
var numCount2 = numbersQuery.Count();
Eftersom Fråga nr 7 returnerar ett enda värde och inte en samling körs frågan omedelbart.
Den tidigare frågan kan skrivas med implicit inmatning med var
, enligt följande:
var numCount = (from num in numbers...
Den kan skrivas i metodsyntaxen på följande sätt:
var numCount = numbers.Count(n => n is > 3 and < 7);
Den kan skrivas med explicit inmatning enligt följande:
int numCount = numbers.Count(n => n is > 3 and < 7);
Ange predikatfilter dynamiskt vid körning
I vissa fall vet du inte förrän i körningen hur många predikat du måste tillämpa på källelement i where
-satsen. Ett sätt att dynamiskt ange flera predikatfilter är att använda Contains metoden, som du ser i följande exempel. Frågan returnerar olika resultat baserat på värdet id
för när frågan körs.
int[] ids = [111, 114, 112];
var queryNames =
from student in students
where ids.Contains(student.ID)
select new
{
student.LastName,
student.ID
};
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/
// Change the ids.
ids = [122, 117, 120, 115];
// The query will now return different results
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
*/
Du kan använda kontrollflödesinstruktioner, till exempel if... else
eller switch
, för att välja bland fördefinierade alternativa frågor. I följande exempel studentQuery
använder en annan where
sats om körningsvärdet oddYear
för är true
eller false
.
void FilterByYearType(bool oddYear)
{
IEnumerable<Student> studentQuery = oddYear
? (from student in students
where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
select student)
: (from student in students
where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
select student);
var descr = oddYear ? "odd" : "even";
Console.WriteLine($"The following students are at an {descr} year level:");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}
FilterByYearType(true);
/* Output:
The following students are at an odd year level:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
*/
FilterByYearType(false);
/* Output:
The following students are at an even year level:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
*/
Hantera null-värden i frågeuttryck
Det här exemplet visar hur du hanterar möjliga null-värden i källsamlingar. En objektsamling, till exempel en IEnumerable<T> kan innehålla element vars värde är null. Om en källsamling är null
eller innehåller ett element vars värde är null
, och frågan inte hanterar null
värden, utlöses en NullReferenceException när du kör frågan.
Du kan koda defensivt för att undvika ett null-referensfel enligt följande exempel:
var query1 =
from c in categories
where c != null
join p in products on c.ID equals p?.CategoryID
select new
{
Category = c.Name,
Name = p.Name
};
I föregående exempel where
filtrerar satsen bort alla null-element i kategorisekvensen. Den här tekniken är oberoende av null-kontrollen i kopplingssatsen. Villkorsuttrycket med null i det här exemplet fungerar eftersom Products.CategoryID
är av typen int?
, vilket är en förkortning för Nullable<int>
.
Om bara en av jämförelsenycklarna är en nullbar värdetyp i en kopplingssats kan du omvandla den andra till en nullbar värdetyp i frågeuttrycket. I följande exempel antar du att det EmployeeID
är en kolumn som innehåller värden av typen int?
:
var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };
I vart och ett av exemplen används frågenyckelordet equals
. Du kan också använda mönstermatchning, som innehåller mönster för is null
och is not null
. Dessa mönster rekommenderas inte i LINQ-frågor eftersom frågeprovidrar kanske inte tolkar den nya C#-syntaxen korrekt. En frågeprovider är ett bibliotek som översätter C#-frågeuttryck till ett inbyggt dataformat, till exempel Entity Framework Core. Frågeprovidrar implementerar System.Linq.IQueryProvider gränssnittet för att skapa datakällor som implementerar System.Linq.IQueryable<T> gränssnittet.
Hantera undantag i frågeuttryck
Det är möjligt att anropa valfri metod i kontexten för ett frågeuttryck. Anropa inte någon metod i ett frågeuttryck som kan skapa en bieffekt, till exempel att ändra innehållet i datakällan eller utlösa ett undantag. Det här exemplet visar hur du undviker att skapa undantag när du anropar metoder i ett frågeuttryck utan att bryta mot de allmänna .NET-riktlinjerna för undantagshantering. Dessa riktlinjer anger att det är acceptabelt att fånga ett specifikt undantag när du förstår varför det genereras i en viss kontext. Mer information finns i Metodtips för undantag.
Det sista exemplet visar hur du hanterar dessa fall när du måste utlösa ett undantag under körningen av en fråga.
I följande exempel visas hur du flyttar undantagshanteringskod utanför ett frågeuttryck. Den här refaktoreringen är bara möjlig när metoden inte är beroende av några variabler som är lokala för frågan. Det är enklare att hantera undantag utanför frågeuttrycket.
// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();
// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation");
}
if (dataSource is not null)
{
// If we get here, it is safe to proceed.
var query =
from i in dataSource
select i * i;
foreach (var i in query)
{
Console.WriteLine(i.ToString());
}
}
I blocket catch (InvalidOperationException)
i föregående exempel hanterar du (eller hanterar inte) undantaget på det sätt som är lämpligt för ditt program.
I vissa fall kan det bästa svaret på ett undantag som genereras från en fråga vara att stoppa frågekörningen omedelbart. I följande exempel visas hur du hanterar undantag som kan genereras inifrån en frågetext. Anta att SomeMethodThatMightThrow
det kan orsaka ett undantag som kräver att frågekörningen stoppas.
Blocket try
omsluter loopen foreach
och inte själva frågan. Loopen foreach
är den punkt där frågan körs. Körningsfel utlöses när frågan körs. Därför måste de hanteras i loopen foreach
.
// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
s[4] == 'C' ?
throw new InvalidOperationException() :
@"C:\newFolder\" + s;
// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];
// Demonstration query that throws.
var exceptionDemoQuery =
from file in files
let n = SomeMethodThatMightThrow(file)
select n;
try
{
foreach (var item in exceptionDemoQuery)
{
Console.WriteLine($"Processing {item}");
}
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
*/
Kom ihåg att fånga det undantag som du förväntar dig att skapa och/eller göra nödvändig rensning i ett finally
block.