Çalışma zamanı durumunu temel alan sorgu
ÇOĞU LINQ sorgusunda, sorgunun genel şekli kodda ayarlanır. Bir yan tümce kullanarak where
öğeleri filtreleyebilirsiniz, kullanarak çıkış koleksiyonunu orderby
sıralayabilir, öğeleri gruplandırabilir veya bazı hesaplamalar gerçekleştirebilirsiniz. Kodunuz filtre veya sıralama anahtarı ya da sorgunun parçası olan diğer ifadeler için parametreler sağlayabilir. Ancak, sorgunun genel şekli değiştirilemez. Bu makalede, çalışma zamanında sorgunun şeklini değiştirmek için bunu uygulayan arabirim ve türleri kullanma System.Linq.IQueryable<T> tekniklerini öğreneceksiniz.
Bazı kullanıcı girişlerinin veya çalışma zamanı durumunun sorgunun parçası olarak kullanmak istediğiniz sorgu yöntemlerini değiştirdiği çalışma zamanında sorgular oluşturmak için bu teknikleri kullanırsınız. Sorgu yan tümceleri ekleyerek, kaldırarak veya değiştirerek sorguyu düzenlemek istiyorsunuz.
Not
.cs dosyanızın en üstüne ve using static System.Linq.Expressions.Expression;
eklediğinizden using System.Linq.Expressions;
emin olun.
Bir veri kaynağına karşı bir IQueryable veya IQueryable<T> tanımlayan kodu göz önünde bulundurun:
string[] companyNames = [
"Consolidated Messenger", "Alpine Ski House", "Southridge Video",
"City Power & Light", "Coho Winery", "Wide World Importers",
"Graphic Design Institute", "Adventure Works", "Humongous Insurance",
"Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee"
];
// Use an in-memory array as the data source, but the IQueryable could have come
// from anywhere -- an ORM backed by a database, a web request, or any other LINQ provider.
IQueryable<string> companyNamesSource = companyNames.AsQueryable();
var fixedQry = companyNames.OrderBy(x => x);
Önceki kodu her çalıştırdığınızda tam olarak aynı sorgu yürütülür. Şimdi sorguyu genişletmeyi veya değiştirmeyi öğrenelim. Temelde, bir IQueryable iki bileşene sahiptir:
- Expression—geçerli sorgu bileşenlerinin ifade ağacı biçiminde dil-bağımsız ve veri kaynağı-agnostik gösterimi.
- Provider—geçerli sorguyu bir değere veya değer kümesine dönüştürmeyi bilen bir LINQ sağlayıcısı örneği.
Dinamik sorgulama bağlamında sağlayıcı genellikle aynı kalır; sorgunun ifade ağacı sorgudan sorguya farklılık gösterir.
İfade ağaçları sabittir; farklı bir ifade ağacı ve dolayısıyla farklı bir sorgu istiyorsanız, var olan ifade ağacını yeni bir ifade ağacına çevirmeniz gerekir. Aşağıdaki bölümlerde, çalışma zamanı durumuna yanıt olarak farklı sorgulamaya yönelik belirli teknikler açıklanmaktadır:
- İfade ağacının içinden çalışma zamanı durumunu kullanma
- Daha fazla LINQ yöntemi çağırma
- LINQ yöntemlerine geçirilen ifade ağacını değiştirin
- konumundaki fabrika yöntemlerini kullanarak bir Expression<TDelegate> ifade ağacı oluşturma Expression
- ' IQueryablenin ifade ağacına yöntem çağrı düğümleri ekleme
- Dizeler oluşturun ve Dinamik LINQ kitaplığını kullanın
Tekniklerin her biri daha fazla özellik sağlar, ancak karmaşıklığın artmasına neden olur.
İfade ağacının içinden çalışma zamanı durumunu kullanma
Dinamik olarak sorgulamanın en basit yolu, aşağıdaki kod örneğinde olduğu gibi length
kapalı bir değişken aracılığıyla doğrudan sorgudaki çalışma zamanı durumuna başvurmaktır:
var length = 1;
var qry = companyNamesSource
.Select(x => x.Substring(0, length))
.Distinct();
Console.WriteLine(string.Join(",", qry));
// prints: C, A, S, W, G, H, M, N, B, T, L, F
length = 2;
Console.WriteLine(string.Join(",", qry));
// prints: Co, Al, So, Ci, Wi, Gr, Ad, Hu, Wo, Ma, No, Bl, Tr, Th, Lu, Fo
İç ifade ağacı ve dolayısıyla sorgu değiştirilmez; sorgu, yalnızca değeri değiştirildiğinden farklı değerler length
döndürür.
Daha fazla LINQ yöntemi çağırma
Genel olarak, içindeki yerleşik LINQ yöntemleriQueryable iki adım gerçekleştirir:
- Geçerli ifade ağacını yöntem çağrısını temsil eden bir MethodCallExpression içinde sarmalar.
- Sağlayıcının yöntemi aracılığıyla bir değer döndürmek veya yöntemi aracılığıyla çevrilmiş bir sorgu nesnesi döndürmek için sarmalanmış ifade ağacını sağlayıcıya IQueryProvider.ExecuteIQueryProvider.CreateQuery geri geçirin.
Yeni bir System.Linq.IQueryable<T>sorgu almak için özgün sorguyu -returning yönteminin sonucuyla değiştirebilirsiniz. Aşağıdaki örnekte olduğu gibi çalışma zamanı durumunu kullanabilirsiniz:
// bool sortByLength = /* ... */;
var qry = companyNamesSource;
if (sortByLength)
{
qry = qry.OrderBy(x => x.Length);
}
LINQ yöntemlerine geçirilen ifade ağacını değiştirin
Çalışma zamanı durumuna bağlı olarak LINQ yöntemlerine farklı ifadeler geçirebilirsiniz:
// string? startsWith = /* ... */;
// string? endsWith = /* ... */;
Expression<Func<string, bool>> expr = (startsWith, endsWith) switch
{
("" or null, "" or null) => x => true,
(_, "" or null) => x => x.StartsWith(startsWith),
("" or null, _) => x => x.EndsWith(endsWith),
(_, _) => x => x.StartsWith(startsWith) || x.EndsWith(endsWith)
};
var qry = companyNamesSource.Where(expr);
LinqKit'in PredicateBuilder'ı gibi başka bir kitaplık kullanarak çeşitli alt ifadeleri de oluşturmak isteyebilirsiniz:
// This is functionally equivalent to the previous example.
// using LinqKit;
// string? startsWith = /* ... */;
// string? endsWith = /* ... */;
Expression<Func<string, bool>>? expr = PredicateBuilder.New<string>(false);
var original = expr;
if (!string.IsNullOrEmpty(startsWith))
{
expr = expr.Or(x => x.StartsWith(startsWith));
}
if (!string.IsNullOrEmpty(endsWith))
{
expr = expr.Or(x => x.EndsWith(endsWith));
}
if (expr == original)
{
expr = x => true;
}
var qry = companyNamesSource.Where(expr);
Fabrika yöntemlerini kullanarak ifade ağaçları ve sorguları oluşturma
Bu noktaya kadar olan tüm örneklerde, derleme zamanındastring
öğe türünü ve dolayısıyla sorgununIQueryable<string>
türünü bilirsiniz. Herhangi bir öğe türündeki bir sorguya bileşen ekleyebilir veya öğe türüne bağlı olarak farklı bileşenler ekleyebilirsiniz. konumundaki fabrika yöntemlerini System.Linq.Expressions.Expressionkullanarak sıfırdan ifade ağaçları oluşturabilir ve böylece çalışma zamanında ifadeyi belirli bir öğe türüne uyarlayabilirsiniz.
İfade<TDelegate Oluşturma>
LINQ yöntemlerinden birine geçirmek için bir ifade oluşturduğunuzda, örneğini oluşturursunuz; burada TDelegate
, Action
gibi Func<string, bool>
bir System.Linq.Expressions.Expression<TDelegate>temsilci türü veya özel bir temsilci türüdür.
System.Linq.Expressions.Expression<TDelegate> aşağıdaki örnekte olduğu gibi tam bir lambda ifadesini temsil eden öğesinden LambdaExpressiondevralır:
Expression<Func<string, bool>> expr = x => x.StartsWith("a");
A'nın LambdaExpression iki bileşeni vardır:
- Özelliğiyle Parameters temsil edilen bir parametre listesi
(string x)
. - Özelliğiyle Body temsil edilen bir gövde
x.StartsWith("a")
.
oluşturmanın Expression<TDelegate> temel adımları şunlardır:
- Factory yöntemini kullanarak Parameter lambda ifadesindeki parametrelerin (varsa) her biri için nesneleri tanımlayınParameterExpression.
ParameterExpression x = Parameter(typeof(string), "x");
- öğesinin gövdesini LambdaExpression, konumundaki ParameterExpression tanımlı ve fabrika yöntemlerini Expressionkullanarak oluşturun. Örneğin, temsil eden
x.StartsWith("a")
bir ifade şu şekilde oluşturulabilir:Expression body = Call( x, typeof(string).GetMethod("StartsWith", [typeof(string)])!, Constant("a") );
- Parametreleri ve gövdeyi uygun Lambda fabrika yöntemi aşırı yüklemesini kullanarak derleme zamanı türünde bir İfade<TDelegate> içinde sarmalar:
Expression<Func<string, bool>> expr = Lambda<Func<string, bool>>(body, x);
Aşağıdaki bölümlerde, linq yöntemine geçirmek için oluşturmak Expression<TDelegate> isteyebileceğiniz bir senaryo açıklanmaktadır. Fabrika yöntemlerini kullanarak bunun nasıl yapılacağını gösteren eksiksiz bir örnek sağlar.
Çalışma zamanında tam sorgu oluşturma
Birden çok varlık türüyle çalışan sorgular yazmak istiyorsunuz:
record Person(string LastName, string FirstName, DateTime DateOfBirth);
record Car(string Model, int Year);
Bu varlık türlerinden herhangi biri için, yalnızca alanlarından birinde string
belirli bir metni olan varlıkları filtrelemek ve döndürmek istiyorsunuz. için Person
ve LastName
özelliklerinde FirstName
arama yapmak istersiniz:
string term = /* ... */;
var personsQry = new List<Person>()
.AsQueryable()
.Where(x => x.FirstName.Contains(term) || x.LastName.Contains(term));
Ancak için Car
yalnızca Model
özelliğini aramak istersiniz:
string term = /* ... */;
var carsQry = new List<Car>()
.AsQueryable()
.Where(x => x.Model.Contains(term));
için IQueryable<Person>
bir özel işlev ve için IQueryable<Car>
başka bir özel işlev yazabilirsiniz ancak aşağıdaki işlev, belirli öğe türünden bağımsız olarak bu filtrelemeyi mevcut sorgulara ekler.
// using static System.Linq.Expressions.Expression;
IQueryable<T> TextFilter<T>(IQueryable<T> source, string term)
{
if (string.IsNullOrEmpty(term)) { return source; }
// T is a compile-time placeholder for the element type of the query.
Type elementType = typeof(T);
// Get all the string properties on this specific type.
PropertyInfo[] stringProperties = elementType
.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.ToArray();
if (!stringProperties.Any()) { return source; }
// Get the right overload of String.Contains
MethodInfo containsMethod = typeof(string).GetMethod("Contains", [typeof(string)])!;
// Create a parameter for the expression tree:
// the 'x' in 'x => x.PropertyName.Contains("term")'
// The type of this parameter is the query's element type
ParameterExpression prm = Parameter(elementType);
// Map each property to an expression tree node
IEnumerable<Expression> expressions = stringProperties
.Select(prp =>
// For each property, we have to construct an expression tree node like x.PropertyName.Contains("term")
Call( // .Contains(...)
Property( // .PropertyName
prm, // x
prp
),
containsMethod,
Constant(term) // "term"
)
);
// Combine all the resultant expression nodes using ||
Expression body = expressions
.Aggregate((prev, current) => Or(prev, current));
// Wrap the expression body in a compile-time-typed lambda expression
Expression<Func<T, bool>> lambda = Lambda<Func<T, bool>>(body, prm);
// Because the lambda is compile-time-typed (albeit with a generic parameter), we can use it with the Where method
return source.Where(lambda);
}
İşlev bir (yalnızca bir IQueryable<T> değil) aldığından TextFilter
ve döndürdüğündenIQueryable, metin filtresinden sonra daha fazla derleme zamanı türündeki sorgu öğesi ekleyebilirsiniz.
var qry = TextFilter(
new List<Person>().AsQueryable(),
"abcd"
)
.Where(x => x.DateOfBirth < new DateTime(2001, 1, 1));
var qry1 = TextFilter(
new List<Car>().AsQueryable(),
"abcd"
)
.Where(x => x.Year == 2010);
IQueryable<TDelegate'in> ifade ağacına yöntem çağrı düğümleri ekleme
yerine bir IQueryableIQueryable<T>varsa, genel LINQ yöntemlerini doğrudan çağıramazsınız. Bir alternatif, önceki örnekte gösterildiği gibi iç ifade ağacını oluşturmak ve ifade ağacını geçirirken uygun LINQ yöntemini çağırmak için yansımayı kullanmaktır.
LinQ yönteminin işlevselliğini, linq yöntemine yapılan çağrıyı temsil eden bir MethodCallExpression ağaçta sarmalayarak da çoğaltabilirsiniz:
IQueryable TextFilter_Untyped(IQueryable source, string term)
{
if (string.IsNullOrEmpty(term)) { return source; }
Type elementType = source.ElementType;
// The logic for building the ParameterExpression and the LambdaExpression's body is the same as in the previous example,
// but has been refactored into the constructBody function.
(Expression? body, ParameterExpression? prm) = constructBody(elementType, term);
if (body is null) { return source; }
Expression filteredTree = Call(
typeof(Queryable),
"Where",
[elementType],
source.Expression,
Lambda(body, prm!)
);
return source.Provider.CreateQuery(filteredTree);
}
Bu durumda, derleme zamanı genel yer tutucunuz yoktur, bu nedenle derleme zamanı T
türü bilgileri gerektirmeyen ve yerine Expression<TDelegate>bir LambdaExpression oluşturan aşırı yüklemeyi kullanırsınızLambda.
Dinamik LINQ kitaplığı
fabrika yöntemlerini kullanarak ifade ağaçları oluşturmak nispeten karmaşıktır; dize oluşturmak daha kolaydır. Dinamik LINQ kitaplığı, üzerindeki IQueryable standart LINQ yöntemlerine karşılık gelen ve dizeleri ifade ağaçları yerine özel bir söz diziminde kabul eden bir uzantı yöntemleri Queryablekümesini kullanıma sunar. Kitaplık, dizeden uygun ifade ağacını oluşturur ve sonuç çevirisini IQueryabledöndürebilir.
Örneğin, önceki örnek aşağıdaki gibi yeniden yazılabilir:
// using System.Linq.Dynamic.Core
IQueryable TextFilter_Strings(IQueryable source, string term)
{
if (string.IsNullOrEmpty(term)) { return source; }
var elementType = source.ElementType;
// Get all the string property names on this specific type.
var stringProperties =
elementType.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.ToArray();
if (!stringProperties.Any()) { return source; }
// Build the string expression
string filterExpr = string.Join(
" || ",
stringProperties.Select(prp => $"{prp.Name}.Contains(@0)")
);
return source.Where(filterExpr, term);
}