{"id":488597,"date":"2026-07-26T11:11:28","date_gmt":"2026-07-26T11:11:28","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=488597"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=488597","title":{"rendered":"\u0413\u0438\u0431\u043a\u0430\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f EF Core \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Expression. \u0427\u0430\u0441\u0442\u044c 2: Roslyn Source Generator"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 <a href=\"https:\/\/habr.com\/ru\/articles\/851874\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/a> \u043c\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u0440\u0430\u043d\u0442\u0430\u0439\u043c-\u0431\u0438\u043b\u0434\u0435\u0440 Expression-\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 Entity Framework. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e, \u043d\u043e \u0438\u043c\u0435\u043b\u043e \u0440\u044f\u0434 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439:\\<\/p>\n<ul>\n<li>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 (IConstantExpressionHandler). \u2011 \u041d\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u043d\u0443\u0436\u043d\u043e\u00a0\u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u00a0\u043a\u043e\u0434 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0438\u043b\u0438\u00a0\u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u00a0\u0431\u044b\u043b\u043e \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438\u00a0\u2014 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430 \u0432\u00a0\u0438\u043c\u0435\u043d\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u043b\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u00a0\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f ([ComputedField]) \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044e \u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 Expression\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430. \u042d\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u043b\u043e \u0445\u043e\u0442\u044c \u0438 \u043d\u0435 \u0441\u0438\u043b\u044c\u043d\u043e\u0435, \u043d\u043e \u0432\u0441\u0435 \u0442\u0430\u043a\u0438 \u0437\u0430\u043c\u0435\u0434\u043b\u0435\u043d\u0438\u044f \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043b\u044e\u0441, \u043a\u0430\u043a \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0435 \u0438\u043c\u0435\u043b \u0447\u0435\u0442\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c \u0442\u0438\u043f \u043f\u043e\u043b\u044f \u0438\u043b\u0438 \u0435\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435. <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u043e\u0439\u0434\u0451\u043c \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438 \u043f\u0430\u0440\u0441\u0438\u0442 Expression\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 POCO\u2011\u043a\u043b\u0430\u0441\u0441 \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f Apply(). \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u00a0\u2014 zero\u2011runtime\u2011cost \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439, \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 multi\u2011column \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: \u043f\u043e\u0447\u0435\u043c\u0443 runtime-\u0431\u0438\u043b\u0434\u0435\u0440 \u2014 \u043d\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412\u0435\u0440\u043d\u0451\u043c\u0441\u044f \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438. \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440 \u0434\u043b\u044f \u043a\u043d\u0438\u0433:<\/p>\n<pre><code class=\"json\">{  \"filters\": [    { \"propName\": \"Year\", \"value\": \"2024\" },    { \"propName\": \"PublisherId\", \"value\": \"1234\" }  ]}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u0440\u043e\u0438\u0442 Expression \u0447\u0435\u0440\u0435\u0437 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044e:<\/p>\n<pre><code class=\"cs\">var prop = typeof(T).GetProperty(propertyName);var handler = propType switch{    _ when propType == typeof(Guid) =&gt; new GuidConstantExpressionHandler(),    _ when propType == typeof(int) =&gt; new IntegerConstantExpressionHandler(),    _ when propType == typeof(string) =&gt; new StringConstantExpressionHandler(),    _ =&gt; throw new ArgumentOutOfRangeException()};<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e:<\/p>\n<ol>\n<li>\n<p><strong>\u041d\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0442\u0438\u043f\u043e\u0432.<\/strong> \u0415\u0441\u043b\u0438 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043b\u0438 <code>\"propName\": \"Amont\"<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>\"Amount\"<\/code>, \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u0441\u043f\u043b\u044b\u0432\u0451\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439.<\/strong> \u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e <code>Customer.Name<\/code> \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f null-guard \u0446\u0435\u043f\u043e\u0447\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.<\/strong> \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437 \u0438\u0441\u0442\u043e\u0440\u0438\u0438) \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0447\u0435\u0440\u0435\u0437 <code>[ComputedField]<\/code> \u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u2014 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0441\u044e \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0438\u0437 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0430 \u0432 <strong>\u044d\u0442\u0430\u043f \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438<\/strong>.<\/p>\n<h3>Roslyn Source Generator: \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u043a\u0430\u043a \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a<\/h3>\n<p>Roslyn \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 C# \u0438, \u0447\u0442\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0441\u0435\u0439\u0447\u0430\u0441 \u0441\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435, \u043e\u043d \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 <strong>Source Generators<\/strong> \u2014 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438 \u0438 \u043c\u043e\u0436\u0435\u0442 \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439.<\/p>\n<p>\u0418\u0434\u0435\u044f \u043f\u0440\u043e\u0441\u0442\u0430: \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u0438\u0441\u0430\u0442\u044c if\/else \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435, \u043c\u044b \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 \u0447\u0435\u0440\u0435\u0437 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0430 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0441\u0430\u043c \u0441\u0442\u0440\u043e\u0438\u0442 \u043a\u043e\u0434 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438.<\/p>\n<h4>\u0428\u0430\u0433 1: \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b<\/h4>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0434\u0432\u0443\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432:<\/p>\n<pre><code class=\"cs\">\/\/ \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0434\u043b\u044f \u043a\u0430\u043a\u043e\u0439 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0444\u0438\u043b\u044c\u0442\u0440[AttributeUsage(AttributeTargets.Class, Inherited = false)]public class GenerateFilterAttribute(Type entityType) : Attribute{    Type EntityType = entityType;    \/\/ \u041e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e: \u044f\u0432\u043d\u043e\u0435 \u0438\u043c\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430    \/\/ \u0415\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u043e \u2014 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 {EntityName}FilterParams    public string? ClassName { get; set; }}\/\/ \u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]public class CompareAttribute : Attribute{    public CompareOperator Operator { get; }    public CompareAttribute(CompareOperator op) =&gt; Operator = op;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 enum \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432:<\/p>\n<pre><code class=\"cs\">public enum CompareOperator{    Equal,    NotEqual,    GreaterThan,    GreaterThanOrEqual,    LessThan,    LessThanOrEqual,    Contains,    StartsWith,    EndsWith,}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 2: \u041e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430<\/h4>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e JSON-\u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 <strong>\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430<\/strong> \u043a\u0430\u043a <code>partial class<\/code> \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u0430\u043c\u0438:<\/p>\n<pre><code class=\"cs\">[GenerateFilter(typeof(Order))]public partial class OrderFilterDefinition{    \/\/ Equal (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f Guid?)    public static Expression&lt;Func&lt;Order, Guid?&gt;&gt;? CustomerId { get; } = o =&gt; o.CustomerId;    \/\/ Contains (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f string)    public static Expression&lt;Func&lt;Order, string&gt;&gt;? Description { get; } = o =&gt; o.Description;    \/\/ GreaterThanOrEqual \u2014 \u0447\u0435\u0440\u0435\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442    [Compare(CompareOperator.GreaterThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MinAmount { get; } = o =&gt; o.Amount;    \/\/ LessThanOrEqual    [Compare(CompareOperator.LessThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MaxAmount { get; } = o =&gt; o.Amount;    \/\/ \u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u2014 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 null-guard    public static Expression&lt;Func&lt;Order, string&gt;&gt;? CustomerName { get; } = o =&gt; o.Customer.Name;    \/\/ \u041c\u0435\u0442\u043e\u0434-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442    public static Expression&lt;Func&lt;Order, bool&gt;&gt;? HasItemWithCategory(OrderFilterDefinition filter) =&gt;        filter.ItemId.HasValue            ? o =&gt; o.OrderItems.Any(i =&gt; i.CategoryId == filter.CategoryId.Value)            : null;    \/\/ Instance-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u0439 \u043c\u0435\u0442\u043e\u0434\u043e\u0432-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u043e\u0432    public Guid? CategoryId { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u2014 \u044d\u0442\u043e \u043b\u044f\u043c\u0431\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u043b\u044e \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0432\u0438\u0434\u0438\u0442 \u044d\u0442\u043e \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043a syntax tree \u0438 \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c.<\/p>\n<h4>\u0428\u0430\u0433 3: \u041f\u0430\u0440\u0441\u0438\u043d\u0433 Expression \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438<\/h4>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u2014 \u044d\u0442\u043e <code>IIncrementalGenerator<\/code>. \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430:<\/p>\n<pre><code class=\"cs\">[Generator]public sealed partial class FilterGenerator : IIncrementalGenerator{    private const string GenerateFilterAttrFqn =        \"GreenNide.ExpressionFilter.GenerateFilterAttribute\";    public void Initialize(IncrementalGeneratorInitializationContext context)    {        var results = context.SyntaxProvider            .ForAttributeWithMetadataName(                GenerateFilterAttrFqn,                predicate: static (node, ct) =&gt; node is ClassDeclarationSyntax,                transform: static (ctx, ct) =&gt; ParseFilterClass(ctx, ct))            .Where(static r =&gt; r is not null);        context.RegisterSourceOutput(            results.Collect(),            static (spc, results) =&gt;            {                foreach (var result in results)                {                    if (result is null) continue;                    foreach (var diag in result.Diagnostics)                        spc.ReportDiagnostic(diag);                    if (result.Definition is not null)                    {                        var code = GenerateCode(result.Definition);                        spc.AddSource(                            $\"{result.Definition.GeneratedClassName}.Filter.g.cs\", code);                    }                }            });    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>ForAttributeWithMetadataName<\/code> \u2014 \u044d\u0442\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0440\u0430\u0437\u0443 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u0442 \u043a\u043b\u0430\u0441\u0441\u044b \u0441 \u043d\u0443\u0436\u043d\u044b\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u0431\u0438\u0440\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u0432 \u0440\u0435\u0448\u0435\u043d\u0438\u0438.<\/p>\n<p>\u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u043d\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044e:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0423\u0441\u043b\u043e\u0432\u0438\u0435<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u0440\u0438\u043c\u0435\u0440<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>ClassName<\/code> \u0437\u0430\u0434\u0430\u043d \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0441\u0442\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>[GenerateFilter(..., ClassName = \"MyFilter\")]<\/code> \u2192 <code>MyFilter<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>{EntityName}FilterParams<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>[GenerateFilter(typeof(Order))]<\/code> \u2192 <code>OrderFilterParams<\/code><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h3>\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0443\u0442\u0438 \u0438\u0437 \u043b\u044f\u043c\u0431\u0434\u044b<\/h3>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043b\u044f\u043c\u0431\u0434\u0443 <code>o =&gt; o.Customer.Name<\/code> \u0432 \u0441\u0442\u0440\u043e\u043a\u0443 <code>\"Customer.Name\"<\/code>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u0445\u043e\u0434\u0438\u043c <strong>syntax tree<\/strong> \u043b\u044f\u043c\u0431\u0434\u044b:<\/p>\n<pre><code class=\"cs\">private static string WalkExpression(ExpressionSyntax expr, string paramName){    switch (expr)    {        case IdentifierNameSyntax id when id.Identifier.Text == paramName:            return \"\";        case MemberAccessExpressionSyntax member:            var left = WalkExpression(member.Expression, paramName);            var right = member.Name.Identifier.Text;            return string.IsNullOrEmpty(left) ? right : $\"{left}.{right}\";        case InvocationExpressionSyntax inv:            var invoked = WalkExpression(inv.Expression, paramName);            \/\/ \u0414\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0446\u0435\u043f\u043e\u0447\u0435\u043a (LINQ-\u043c\u0435\u0442\u043e\u0434\u043e\u0432) \u2014            \/\/ \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 \"e\" \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442            return ReconstructChain(expr, paramName);        case CastExpressionSyntax cast:            return WalkExpression(cast.Expression, paramName);        default:            return expr.ToString().Replace($\"{paramName}.\", \"\");    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0420\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u044b\u0439 \u043e\u0431\u0445\u043e\u0434 <code>MemberAccessExpressionSyntax<\/code> \u0441\u0442\u0440\u043e\u0438\u0442 \u043f\u0443\u0442\u044c \u043f\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430\u043c: <code>o<\/code> \u2192 <code>\"\"<\/code>, <code>o.Customer<\/code> \u2192 <code>\"Customer\"<\/code>, <code>o.Customer.Name<\/code> \u2192 <code>\"<\/code><a href=\"http:\/\/Customer.Name\" rel=\"noopener noreferrer nofollow\"><code>Customer.Name<\/code><\/a><code>\"<\/code>.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0438\u0434\u0430:<\/p>\n<pre><code class=\"cs\">o =&gt; o.History    .OrderByDescending(h =&gt; h.Timestamp)    .Select(h =&gt; (OrderStatus?)h.Status)    .FirstOrDefault()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0420\u0435\u043a\u0443\u0440\u0441\u0438\u044f \u043d\u0435 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u2014 \u0442\u0443\u0442 <code>InvocationExpressionSyntax<\/code> \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438-\u043b\u044f\u043c\u0431\u0434\u0430\u043c\u0438. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>ReconstructChain()<\/code> \u2014 \u0431\u0435\u0440\u0451\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 <code>e<\/code>:<\/p>\n<pre><code class=\"cs\">private static string ReconstructChain(ExpressionSyntax expr, string paramName){    return expr.ToString().Replace($\"{paramName}.\", \"\");}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443:<\/p>\n<pre><code class=\"cs\">History.OrderByDescending(h =&gt; h.Timestamp)    .Select(h =&gt; (OrderStatus?)h.Status)    .FirstOrDefault()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <code>Where()<\/code> \u043a\u0430\u043a \u0435\u0441\u0442\u044c \u2014 EF Core \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u0432 SQL-\u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441.<\/p>\n<h3>\u0410\u0432\u0442\u043e-\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430<\/h3>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043d\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f. \u041e\u043d \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0435\u0433\u043e \u043f\u043e \u0442\u0438\u043f\u0443 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f Expression:<\/p>\n<pre><code class=\"cs\">private static CompareOperator InferDefaultOperator(ITypeSymbol returnType){    if (returnType.SpecialType == SpecialType.System_String)        return CompareOperator.Contains;    return CompareOperator.Equal;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<ul>\n<li>\n<p><code>string<\/code> \u2192 <code>Contains<\/code> (\u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435 \u2014 \u0441\u0430\u043c\u044b\u0439 \u0447\u0430\u0441\u0442\u044b\u0439 \u043a\u0435\u0439\u0441).<\/p>\n<\/li>\n<li>\n<p>\u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u2192 <code>Equal<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 <code>[Compare(...)]<\/code>:<\/p>\n<pre><code class=\"cs\">[Compare(CompareOperator.GreaterThanOrEqual)]public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MinAmount { get; } = o =&gt; o.Amount;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 4: \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u0434\u0430<\/h4>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 POCO-\u043a\u043b\u0430\u0441\u0441 \u0438 extension-\u043a\u043b\u0430\u0441\u0441 \u0441 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>Apply()<\/code>:<\/p>\n<h3>\u041a\u043b\u0430\u0441\u0441 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434:<\/h3>\n<pre><code class=\"cs\">public class OrderFilterParams{    public Guid? CustomerId { get; set; }    public string? Description { get; set; }    public decimal? MinAmount { get; set; }    public decimal? MaxAmount { get; set; }    public string? CustomerName { get; set; }    public Guid? CategoryId { get; set; }    \/\/ ... \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437 Expression-\u043f\u043e\u043b\u0435\u0439 \u0438 closure}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>Extension-\u043a\u043b\u0430\u0441\u0441 \u0441 \u043c\u0435\u0442\u043e\u0434\u043e\u043c Apply()<\/h3>\n<pre><code class=\"cs\">public static class OrderFilterParamsExtensions{    public static IQueryable&lt;Order&gt; Apply(        this IQueryable&lt;Order&gt; query,        OrderFilterParams filter)    {        if (filter is null) return query;        if (filter.CustomerId.HasValue)            query = query.Where(e =&gt; e.CustomerId == filter.CustomerId.Value);        if (!string.IsNullOrWhiteSpace(filter.Description))            query = query.Where(e =&gt; e.Description.Contains(filter.Description));        if (filter.MinAmount.HasValue)            query = query.Where(e =&gt; e.Amount &gt;= filter.MinAmount.Value);        if (filter.MaxAmount.HasValue)            query = query.Where(e =&gt; e.Amount &lt;= filter.MaxAmount.Value);        if (!string.IsNullOrWhiteSpace(filter.CustomerName))            query = query.Where(e =&gt;                e.Customer != null &amp;&amp;                e.Customer.Name.Contains(filter.CustomerName));        var __def = new OrderFilterDefinition();        __def.ItemId = filter.ItemId;        var __pred_HasItem = OrderFilterDefinition.HasItemWithCaategory(__def);        if (__pred_HasItem != null)            query = query.Where(__pred_HasItem);        return query;    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 null-guard: <code>e.Customer != null<\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0441\u0432\u043e\u0439\u0441\u0442\u0432. \u0415\u0441\u043b\u0438 \u043f\u0443\u0442\u044c \u2014 <code>Customer.Name<\/code>, \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u043f\u043e \u0442\u043e\u0447\u043a\u0430\u043c \u0438 \u0441\u0442\u0440\u043e\u0438\u0442 \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a:<\/p>\n<pre><code>\"Customer.Name\"           \u2192 \"e.Customer != null\"\"Order.Customer.Address\"  \u2192 \"e.Order != null &amp;&amp; e.Order.Customer != null\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 5: Nullable-\u0442\u0438\u043f\u044b<\/h4>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 nullable value types (<code>int?<\/code>, <code>decimal?<\/code>, <code>Guid?<\/code>, <code>DateTime?<\/code>):<\/p>\n<ul>\n<li>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 <code>filter.X.HasValue<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f <code>.Value<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f <code>string?<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>string.IsNullOrWhiteSpace()<\/code>. \u0414\u043b\u044f \u0441\u0441\u044b\u043b\u043e\u0447\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u2014 <code>!= null<\/code>.<\/p>\n<p>\u042d\u0442\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e \u0442\u0438\u043f\u0443 Expression:<\/p>\n<pre><code class=\"cs\">var isNullableValueType = returnType.IsValueType    &amp;&amp; returnType.NullableAnnotation == NullableAnnotation.Annotated;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 6: Multi-column Search<\/h4>\n<p>\u0427\u0430\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c. \u0412 \u043d\u0430\u0448\u0435\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u0435 \u044d\u0442\u043e Expression \u0441 \u0442\u0438\u043f\u043e\u043c <code>string[]<\/code>:<\/p>\n<pre><code class=\"cs\">public static Expression&lt;Func&lt;Order, string[]&gt;&gt;? Search { get; } =    o =&gt; new[] { o.Description, o.Customer.Name, o.Customer.Email };<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u0447\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0439 \u0442\u0438\u043f \u2014 <code>string[]<\/code>, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0438 \u0441\u0442\u0440\u043e\u0438\u0442 OR-\u0443\u0441\u043b\u043e\u0432\u0438\u0435:<\/p>\n<pre><code class=\"cs\">if (!string.IsNullOrWhiteSpace(filter.Search)){    query = query.Where(e =&gt;        e.Description.Contains(filter.Search) ||        (e.Customer != null &amp;&amp; e.Customer.Name.Contains(filter.Search)) ||        (e.Customer != null &amp;&amp; e.Customer.Email.Contains(filter.Search)));}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 null-guard \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439. \u0415\u0441\u043b\u0438 <code>Customer<\/code> \u2014 <code>null<\/code>, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 <code>NullReferenceException<\/code>.<\/p>\n<p>\u042f \u043f\u043e\u043d\u0438\u043c\u0430\u044e, \u0447\u0442\u043e \u0434\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043a\u0435\u0439\u0441\u0430 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 PG \u044d\u0442\u043e tsvector \u043f\u043e \u044d\u0442\u0438\u043c \u043f\u043e\u043b\u044f\u043c. \u041d\u043e \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f \u0442\u043e, \u0447\u0442\u043e \u043f\u043e\u043b\u044f \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u0445 &#8212; \u043d\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0441\u0430\u043c\u0438\u043c \u043f\u0435\u0440\u0435\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0435\u0433\u043e. \u042f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u044d\u0442\u043e\u0442 \u0444\u0438\u043b\u044c\u0442\u0440 \u0434\u043b\u044f \u0441\u0432\u043e\u0438\u0445 \u0446\u0435\u043b\u0435\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u043d\u0435 \u0432\u043f\u043e\u043b\u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0438\u0441\u043a, \u0438\u0431\u043e \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0440\u0435\u0434\u043a\u043e, \u0438 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u0438\u043b\u044c\u043d\u043e \u043d\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0411\u0414.<\/p>\n<h4>\u0428\u0430\u0433 7: \u041c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b<\/h4>\n<p>\u0418\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e <code>Where(x =&gt; x.Field == value)<\/code> \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: \u00ab\u043d\u0430\u0439\u0442\u0438 \u0437\u0430\u043a\u0430\u0437\u044b, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0435\u0441\u0442\u044c \u0442\u043e\u0432\u0430\u0440 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u043c Id\u00bb. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 <strong>\u043c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b<\/strong> \u2014 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0441 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u043e\u0439 <code>Expression&lt;Func&lt;TEntity, bool&gt;&gt;?<\/code>:<\/p>\n<pre><code class=\"cs\">\/\/ \u0412 OrderFilterDefinition (\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430)public static Expression&lt;Func&lt;Order, bool&gt;&gt;? HasItemWithCategory(OrderFilterDefinition filter) =&gt;    filter.ItemId.HasValue        ? o =&gt; o.OrderItems.Any(i =&gt; i.CategoryId == filter.CategoryId.Value)        : null;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043c\u0435\u0442\u043e\u0434\u0430 (1 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 Definition-\u0442\u0438\u043f\u0430, \u0432\u043e\u0437\u0432\u0440\u0430\u0442 <code>Expression&lt;Func&lt;TEntity, bool&gt;&gt;?<\/code>).<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u0445\u043e\u0434\u0438\u0442 \u0442\u0435\u043b\u043e \u043c\u0435\u0442\u043e\u0434\u0430, \u0438\u0449\u0435\u0442 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f <code>filter.PropertyName<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 closure-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 (<code>ItemId<\/code>, <code>MinItemCount<\/code>, \u2026) \u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 POCO-\u043a\u043b\u0430\u0441\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0412 <code>Apply()<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <strong>bridge-\u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/strong> \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 Definition, \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 closure-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b:<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"cs\">var __def = new OrderFilterDefinition();__def.ItemId = filter.ItemId;__def.MinItemCount = filter.MinItemCount;var __pred_HasItem = OrderFilterDefinition.HasItem(__def);if (__pred_HasItem != null)    query = query.Where(__pred_HasItem);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Bridge-\u043f\u0430\u0442\u0442\u0435\u0440\u043d \u043d\u0443\u0436\u0435\u043d \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 Definition-\u043a\u043b\u0430\u0441\u0441\u0435 \u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0442 Definition-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440. \u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 POCO \u043d\u0435 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u0442\u0441\u044f \u043e\u0442 Definition \u2014 \u0441\u0432\u044f\u0437\u044c \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442.<\/p>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b \u0441 <code>Any<\/code>, <code>All<\/code>, <code>Count<\/code> \u0438 \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u2014 \u0432\u0441\u0451, \u0447\u0442\u043e \u0432\u044b\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Expression.<\/p>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: Definition vs Generated<\/h3>\n<pre><code>OrderFilterDefinition                    OrderFilterParams (\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f)\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502 static Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430  \u2502         \u2502 public \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430             \u2502\u2502 static \u043c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b     \u2502         \u2502   (Expression + closure)    \u2502\u2502 instance closure-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430   \u2502         \u2502                             \u2502\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518        \u25b2                                          \u2502        \u2502         new Definition()                 \u2502        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518              Apply() \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043c\u043e\u0441\u0442: \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442              closure-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<ul>\n<li>\n<p><code>OrderFilterDefinition<\/code> \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0441 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><code>OrderFilterParams<\/code> \u2014 standalone POCO \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 (\u043c\u043e\u0436\u043d\u043e \u043c\u0430\u043f\u043f\u0438\u0442\u044c \u0432 DTO)<\/p>\n<\/li>\n<li>\n<p>\u041c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 <strong>\u043d\u0435\u0442 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f<\/strong> \u2014 \u0441\u0432\u044f\u0437\u044c \u0447\u0435\u0440\u0435\u0437 bridge-\u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0432 <code>Apply()<\/code><\/p>\n<\/li>\n<\/ul>\n<h3>\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440<\/h3>\n<p>\u0412\u043e\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"cs\">[GenerateFilter(typeof(Order))]public partial class OrderFilterDefinition{    \/\/ \u041f\u0440\u043e\u0441\u0442\u044b\u0435 \u043f\u043e\u043b\u044f    public static Expression&lt;Func&lt;Order, Guid?&gt;&gt;? CustomerId { get; } = o =&gt; o.CustomerId;    public static Expression&lt;Func&lt;Order, string&gt;&gt;? Description { get; } = o =&gt; o.Description;    \/\/ \u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439    [Compare(CompareOperator.GreaterThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MinAmount { get; } = o =&gt; o.Amount;    [Compare(CompareOperator.LessThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MaxAmount { get; } = o =&gt; o.Amount;    \/\/ \u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0434\u0430\u0442    [Compare(CompareOperator.GreaterThanOrEqual)]    public static Expression&lt;Func&lt;Order, DateTime?&gt;&gt;? FromDate { get; } = o =&gt; o.CreatedAt;    [Compare(CompareOperator.LessThanOrEqual)]    public static Expression&lt;Func&lt;Order, DateTime?&gt;&gt;? ToDate { get; } = o =&gt; o.CreatedAt;    \/\/ \u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u2014 null-guard \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439    public static Expression&lt;Func&lt;Order, string&gt;&gt;? CustomerName { get; } = o =&gt; o.Customer.Name;    \/\/ \u041f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441 \u2014 \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 SQL    public static Expression&lt;Func&lt;Order, OrderStatus?&gt;&gt;? CurrentStatus { get; } =        o =&gt; o.History            .OrderByDescending(h =&gt; h.Timestamp)            .Select(h =&gt; (OrderStatus?)h.Status)            .FirstOrDefault();    \/\/ Multi-column search    public static Expression&lt;Func&lt;Order, string[]&gt;&gt;? Search { get; } =        o =&gt; new[] { o.Description, o.Customer.Name, o.Customer.Email };    \/\/ \u041c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b    public static Expression&lt;Func&lt;Order, bool&gt;&gt;? HasItemWithCategory(OrderFilterDefinition filter) =&gt;        filter.ItemId.HasValue            ? o =&gt; o.OrderItems.Any(i =&gt; i.CategoryId == filter.CategoryId.Value)            : null;    public static Expression&lt;Func&lt;Order, bool&gt;&gt;? HasMinItemCount(OrderFilterDefinition filter) =&gt;        filter.MinItemCount.HasValue            ? o =&gt; o.OrderItems.Count &gt;= filter.MinItemCount.Value            : null;    public static Expression&lt;Func&lt;Order, bool&gt;&gt;? AllItemsExpensive(OrderFilterDefinition filter) =&gt;        filter.MinItemPrice.HasValue            ? o =&gt; o.OrderItems.All(i =&gt; i.Price &gt;= filter.MinItemPrice.Value)            : null;    \/\/ Instance-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f closure    public Guid? CaregoryId { get; set; }    public int? MinItemCount { get; set; }    public decimal? MinItemPrice { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0435:<\/p>\n<pre><code class=\"cs\">var filter = new OrderFilterParams{    MinAmount = 100m,    CustomerName = \"Alice\",    ItemId = itemId}; \/\/ \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 \u0432 \u043a\u043e\u043d\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0438 \u0431\u044b\u0442\u044c \u0440\u0430\u0441\u043f\u0430\u0440\u0448\u0435\u043d\u044b\u043c \u0438\u0437 body \u0437\u0430\u043f\u0440\u043e\u0441\u0430var results = await dbContext.Orders    .Apply(filter)    .ToListAsync();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0447\u0435\u0440\u0435\u0437 Roslyn-\u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">ID<\/p>\n<\/th>\n<th>\n<p align=\"left\">Severity<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>GFG001<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Warning<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u043f\u0443\u0442\u044c \u0438\u0437 Expression. \u041f\u043e\u043b\u0435 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>GFG002<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Error<\/p>\n<\/td>\n<td>\n<p align=\"left\">Expression-\u043f\u043e\u043b\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c expression-bodied \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 (<code>=&gt;<\/code>).<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>GFG003<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Warning<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442. \u041c\u0435\u0442\u043e\u0434 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>GFG004<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Error<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0442\u0438\u043f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0438\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 <code>[GenerateFilter]<\/code>.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u043e\u043f\u0435\u0447\u0430\u0442\u0430\u043b\u0438\u0441\u044c \u0432 \u0438\u043c\u0435\u043d\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 (<code>o.Amont<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>o.Amount<\/code>), \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 <code>GFG001<\/code> \u2014 Warning \u0432 IDE \u0438 \u043f\u043e\u043b\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e. \u0415\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0431\u043b\u043e\u0447\u043d\u044b\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0432\u043c\u0435\u0441\u0442\u043e expression-bodied (<code>get { return ...; }<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>=&gt; ...<\/code>), \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 <code>GFG002<\/code> \u2014 Error, \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0451\u0442.<\/p>\n<h3>\u0421\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u0432<\/h3>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041a\u0440\u0438\u0442\u0435\u0440\u0438\u0439<\/p>\n<\/th>\n<th>\n<p align=\"left\">Runtime-\u0431\u0438\u043b\u0434\u0435\u0440 (\u0427\u0430\u0441\u0442\u044c 1)<\/p>\n<\/th>\n<th>\n<p align=\"left\">Source Generator (\u0427\u0430\u0441\u0442\u044c 2)<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u043d\u0442\u0430\u0439\u043c<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044f + \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 Expression \u043d\u0430 \u043b\u0435\u0442\u0443<\/p>\n<\/td>\n<td>\n<p align=\"left\">Zero-cost: \u043a\u043e\u0434 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0437\u0430\u0440\u0430\u043d\u0435\u0435<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0443\u0447\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 null-guard<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>[ComputedField]<\/code> + \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 Expression<\/p>\n<\/td>\n<td>\n<p align=\"left\">Expression \u0432 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0435<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">Multi-column<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>string[]<\/code> \u0432 Expression<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u041c\u0435\u0442\u043e\u0434\u044b-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0422\u0430\u043a\u043e\u0439 \u0436\u0435 [ComputedField] + Expression<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Expression&lt;Func&lt;T, bool&gt;&gt;<\/code> \u043c\u0435\u0442\u043e\u0434\u044b<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td data-colwidth=\"220\" width=\"220\">\n<p align=\"left\">\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435  \u043f\u043e\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0442\u0438\u043f\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u043e\u0432\u044b\u0439 <code>if<\/code> + \u043d\u043e\u0432\u044b\u0439 handler<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0438<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h3>\u0422\u0435\u0441\u0442\u044b<\/h3>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0443\u0445 \u0443\u0440\u043e\u0432\u043d\u044f\u0445:<\/p>\n<p><strong>Unit-\u0442\u0435\u0441\u0442\u044b \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u0430<\/strong> \u2014 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 Source Generator \u0447\u0435\u0440\u0435\u0437 <code>CSharpGeneratorDriver<\/code> \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442, \u0447\u0442\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f\u043c:<\/p>\n<pre><code class=\"cs\">[Fact]public void Generator_ShouldProduceOutput_WhenFilterClassHasGenerateFilterAttribute(){    var source = @\"        using GreenNide.ExpressionFilter;        namespace TestNamespace;        public class Order { public int Id { get; set; } }        [GenerateFilter(typeof(Order))]        public partial class OrderFilterDefinition        {            public static Expression&lt;Func&lt;Order, int&gt;&gt;? Id { get; } = o =&gt; o.Id;        }\";    var (_, runResult) = GeneratorTestHelper.RunGenerator(source);    Assert.Single(runResult.GeneratedTrees);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/strong> \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442, \u0447\u0442\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 SQL \u0447\u0435\u0440\u0435\u0437 Testcontainers \u0441 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c PostgreSQL:<\/p>\n<pre><code class=\"cs\">[Fact]public async Task Subquery_Filter_ShouldTranslateToSql(){    var result = await _ctx.Orders        .Where(o =&gt; o.History            .OrderByDescending(h =&gt; h.Timestamp)            .Select(h =&gt; (OrderStatus?)h.Status)            .FirstOrDefault() == OrderStatus.Shipped)        .ToListAsync();    Assert.Single(result);    Assert.Equal(\"Order 2 - Premium\", result[0].Description);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Testcontainers \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 <code>postgres:15-alpine<\/code> \u0432 Docker, \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u042d\u0442\u043e \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043e Expression, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u0442\u0440\u043e\u0438\u043c, \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432 \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0438\u0442\u043e\u0433\u0435 \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 SQL.<\/p>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u041c\u044b \u043f\u0435\u0440\u0435\u0448\u043b\u0438 \u043e\u0442 runtime-\u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f Expression \u043a compile-time \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u0434\u0430. \u042d\u0442\u043e \u0434\u0430\u043b\u043e \u043d\u0430\u043c:<\/p>\n<ul>\n<li>\n<p><strong>Zero-cost \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044e<\/strong> \u2014 \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0438 \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439<\/strong> \u2014 null-guard \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 <\/strong>\u2014<strong> <\/strong>\u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043a\u0430\u043a \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 <\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 <a href=\"https:\/\/github.com\/EgorYasinovskiy\/FilterGenerator\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>.<\/p>\n<h4>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430<\/h4>\n<p>\u041f\u0430\u043a\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 <a href=\"https:\/\/www.nuget.org\/packages\/GreenNide.FilterGenerator\/\" rel=\"noopener noreferrer nofollow\">NuGet<\/a>:<\/p>\n<pre><code class=\"bash\">dotnet add package GreenNide.FilterGenerator --prerelease<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1063094\/\">https:\/\/habr.com\/ru\/articles\/1063094\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u0440\u0430\u043d\u0442\u0430\u0439\u043c-\u0431\u0438\u043b\u0434\u0435\u0440 Expression-\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 Entity Framework. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e, \u043d\u043e \u0438\u043c\u0435\u043b\u043e \u0440\u044f\u0434 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439:\\\u041a\u0430\u0436\u0434\u044b\u0439 \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 (IConstantExpressionHandler). \u2011 \u041d\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u043d\u0443\u0436\u043d\u043e\u00a0\u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u00a0\u043a\u043e\u0434 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0438\u043b\u0438\u00a0\u0441\u0435\u0440\u0432\u0438\u0441\u0430.\u041d\u0435\u00a0\u0431\u044b\u043b\u043e \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438\u00a0\u2014 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430 \u0432\u00a0\u0438\u043c\u0435\u043d\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u043b\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u00a0\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438.\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f ([ComputedField]) \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044e \u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 Expression\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430. \u042d\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u043b\u043e \u0445\u043e\u0442\u044c \u0438 \u043d\u0435 \u0441\u0438\u043b\u044c\u043d\u043e\u0435, \u043d\u043e \u0432\u0441\u0435 \u0442\u0430\u043a\u0438 \u0437\u0430\u043c\u0435\u0434\u043b\u0435\u043d\u0438\u044f \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.\u041f\u043b\u044e\u0441, \u043a\u0430\u043a \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0435 \u0438\u043c\u0435\u043b \u0447\u0435\u0442\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c \u0442\u0438\u043f \u043f\u043e\u043b\u044f \u0438\u043b\u0438 \u0435\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u043e\u0439\u0434\u0451\u043c \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438 \u043f\u0430\u0440\u0441\u0438\u0442 Expression\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 POCO\u2011\u043a\u043b\u0430\u0441\u0441 \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f Apply(). \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u00a0\u2014 zero\u2011runtime\u2011cost \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432 \u043d\u0430\u00a0\u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439, \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 multi\u2011column \u043f\u043e\u0438\u0441\u043a\u0430.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: \u043f\u043e\u0447\u0435\u043c\u0443 runtime-\u0431\u0438\u043b\u0434\u0435\u0440 \u2014 \u043d\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u0412\u0435\u0440\u043d\u0451\u043c\u0441\u044f \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438. \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440 \u0434\u043b\u044f \u043a\u043d\u0438\u0433:{  &#171;filters&#187;: [    { &#171;propName&#187;: &#171;Year&#187;, &#171;value&#187;: &#171;2024&#187; },    { &#171;propName&#187;: &#171;PublisherId&#187;, &#171;value&#187;: &#171;1234&#187; }  ]}\u0418 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u0440\u043e\u0438\u0442 Expression \u0447\u0435\u0440\u0435\u0437 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044e:var prop = typeof(T).GetProperty(propertyName);var handler = propType switch{    _ when propType == typeof(Guid) =&gt; new GuidConstantExpressionHandler(),    _ when propType == typeof(int) =&gt; new IntegerConstantExpressionHandler(),    _ when propType == typeof(string) =&gt; new StringConstantExpressionHandler(),    _ =&gt; throw new ArgumentOutOfRangeException()};\u042d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e:\u041d\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0442\u0438\u043f\u043e\u0432. \u0415\u0441\u043b\u0438 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043b\u0438 &#171;propName&#187;: &#171;Amont&#187; \u0432\u043c\u0435\u0441\u0442\u043e &#171;Amount&#187;, \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u0441\u043f\u043b\u044b\u0432\u0451\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.\u041d\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0439. \u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e Customer.Name \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f null-guard \u0446\u0435\u043f\u043e\u0447\u043a\u0438.\u041d\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437 \u0438\u0441\u0442\u043e\u0440\u0438\u0438) \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0447\u0435\u0440\u0435\u0437 [ComputedField] \u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430.\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u2014 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0441\u044e \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0438\u0437 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0430 \u0432 \u044d\u0442\u0430\u043f \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438.Roslyn Source Generator: \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u043a\u0430\u043a \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043aRoslyn \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 C# \u0438, \u0447\u0442\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0441\u0435\u0439\u0447\u0430\u0441 \u0441\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435, \u043e\u043d \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 Source Generators \u2014 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438 \u0438 \u043c\u043e\u0436\u0435\u0442 \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439.\u0418\u0434\u0435\u044f \u043f\u0440\u043e\u0441\u0442\u0430: \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u0438\u0441\u0430\u0442\u044c if\/else \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435, \u043c\u044b \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 \u0447\u0435\u0440\u0435\u0437 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0430 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0441\u0430\u043c \u0441\u0442\u0440\u043e\u0438\u0442 \u043a\u043e\u0434 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438.\u0428\u0430\u0433 1: \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0434\u0432\u0443\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432:\/\/ \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0434\u043b\u044f \u043a\u0430\u043a\u043e\u0439 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0444\u0438\u043b\u044c\u0442\u0440[AttributeUsage(AttributeTargets.Class, Inherited = false)]public class GenerateFilterAttribute(Type entityType) : Attribute{    Type EntityType = entityType;    \/\/ \u041e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e: \u044f\u0432\u043d\u043e\u0435 \u0438\u043c\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430    \/\/ \u0415\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u043e \u2014 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 {EntityName}FilterParams    public string? ClassName { get; set; }}\/\/ \u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]public class CompareAttribute : Attribute{    public CompareOperator Operator { get; }    public CompareAttribute(CompareOperator op) =&gt; Operator = op;}\u0418 enum \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432:public enum CompareOperator{    Equal,    NotEqual,    GreaterThan,    GreaterThanOrEqual,    LessThan,    LessThanOrEqual,    Contains,    StartsWith,    EndsWith,}\u0428\u0430\u0433 2: \u041e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0412\u043c\u0435\u0441\u0442\u043e JSON-\u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u043a\u0430\u043a partial class \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u0430\u043c\u0438:[GenerateFilter(typeof(Order))]public partial class OrderFilterDefinition{    \/\/ Equal (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f Guid?)    public static Expression&lt;Func&lt;Order, Guid?&gt;&gt;? CustomerId { get; } = o =&gt; o.CustomerId;    \/\/ Contains (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f string)    public static Expression&lt;Func&lt;Order, string&gt;&gt;? Description { get; } = o =&gt; o.Description;    \/\/ GreaterThanOrEqual \u2014 \u0447\u0435\u0440\u0435\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442    [Compare(CompareOperator.GreaterThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MinAmount { get; } = o =&gt; o.Amount;    \/\/ LessThanOrEqual    [Compare(CompareOperator.LessThanOrEqual)]    public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MaxAmount { get; } = o =&gt; o.Amount;    \/\/ \u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u2014 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 null-guard    public static Expression&lt;Func&lt;Order, string&gt;&gt;? CustomerName { get; } = o =&gt; o.Customer.Name;    \/\/ \u041c\u0435\u0442\u043e\u0434-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442    public static Expression&lt;Func&lt;Order, bool&gt;&gt;? HasItemWithCategory(OrderFilterDefinition filter) =&gt;        filter.ItemId.HasValue            ? o =&gt; o.OrderItems.Any(i =&gt; i.CategoryId == filter.CategoryId.Value)            : null;    \/\/ Instance-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u0439 \u043c\u0435\u0442\u043e\u0434\u043e\u0432-\u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u043e\u0432    public Guid? CategoryId { get; set; }}\u041a\u0430\u0436\u0434\u043e\u0435 Expression-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u2014 \u044d\u0442\u043e \u043b\u044f\u043c\u0431\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u043b\u044e \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0432\u0438\u0434\u0438\u0442 \u044d\u0442\u043e \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043a syntax tree \u0438 \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c.\u0428\u0430\u0433 3: \u041f\u0430\u0440\u0441\u0438\u043d\u0433 Expression \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u2014 \u044d\u0442\u043e IIncrementalGenerator. \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430:[Generator]public sealed partial class FilterGenerator : IIncrementalGenerator{    private const string GenerateFilterAttrFqn =        &#171;GreenNide.ExpressionFilter.GenerateFilterAttribute&#187;;    public void Initialize(IncrementalGeneratorInitializationContext context)    {        var results = context.SyntaxProvider            .ForAttributeWithMetadataName(                GenerateFilterAttrFqn,                predicate: static (node, ct) =&gt; node is ClassDeclarationSyntax,                transform: static (ctx, ct) =&gt; ParseFilterClass(ctx, ct))            .Where(static r =&gt; r is not null);        context.RegisterSourceOutput(            results.Collect(),            static (spc, results) =&gt;            {                foreach (var result in results)                {                    if (result is null) continue;                    foreach (var diag in result.Diagnostics)                        spc.ReportDiagnostic(diag);                    if (result.Definition is not null)                    {                        var code = GenerateCode(result.Definition);                        spc.AddSource(                            $&#187;{result.Definition.GeneratedClassName}.Filter.g.cs&#187;, code);                    }                }            });    }}ForAttributeWithMetadataName \u2014 \u044d\u0442\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0440\u0430\u0437\u0443 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u0442 \u043a\u043b\u0430\u0441\u0441\u044b \u0441 \u043d\u0443\u0436\u043d\u044b\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u0431\u0438\u0440\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u0432 \u0440\u0435\u0448\u0435\u043d\u0438\u0438.\u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u043d\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044e:\u0423\u0441\u043b\u043e\u0432\u0438\u0435\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u041f\u0440\u0438\u043c\u0435\u0440ClassName \u0437\u0430\u0434\u0430\u043d \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0435\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0441\u0442\u044c[GenerateFilter(&#8230;, ClassName = &#171;MyFilter&#187;)] \u2192 MyFilter\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e{EntityName}FilterParams[GenerateFilter(typeof(Order))] \u2192 OrderFilterParams\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0443\u0442\u0438 \u0438\u0437 \u043b\u044f\u043c\u0431\u0434\u044b\u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043b\u044f\u043c\u0431\u0434\u0443 o =&gt; o.Customer.Name \u0432 \u0441\u0442\u0440\u043e\u043a\u0443 &#171;Customer.Name&#187;. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u0445\u043e\u0434\u0438\u043c syntax tree \u043b\u044f\u043c\u0431\u0434\u044b:private static string WalkExpression(ExpressionSyntax expr, string paramName){    switch (expr)    {        case IdentifierNameSyntax id when id.Identifier.Text == paramName:            return &#171;&#187;;        case MemberAccessExpressionSyntax member:            var left = WalkExpression(member.Expression, paramName);            var right = member.Name.Identifier.Text;            return string.IsNullOrEmpty(left) ? right : $&#187;{left}.{right}&#187;;        case InvocationExpressionSyntax inv:            var invoked = WalkExpression(inv.Expression, paramName);            \/\/ \u0414\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0446\u0435\u043f\u043e\u0447\u0435\u043a (LINQ-\u043c\u0435\u0442\u043e\u0434\u043e\u0432) \u2014            \/\/ \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 &#171;e&#187; \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442            return ReconstructChain(expr, paramName);        case CastExpressionSyntax cast:            return WalkExpression(cast.Expression, paramName);        default:            return expr.ToString().Replace($&#187;{paramName}.&#187;, &#171;&#187;);    }}\u0420\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u044b\u0439 \u043e\u0431\u0445\u043e\u0434 MemberAccessExpressionSyntax \u0441\u0442\u0440\u043e\u0438\u0442 \u043f\u0443\u0442\u044c \u043f\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430\u043c: o \u2192 &#171;&#187;, o.Customer \u2192 &#171;Customer&#187;, o.Customer.Name \u2192 &#171;Customer.Name&#187;.\u0414\u043b\u044f \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0438\u0434\u0430:o =&gt; o.History    .OrderByDescending(h =&gt; h.Timestamp)    .Select(h =&gt; (OrderStatus?)h.Status)    .FirstOrDefault()\u0420\u0435\u043a\u0443\u0440\u0441\u0438\u044f \u043d\u0435 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u2014 \u0442\u0443\u0442 InvocationExpressionSyntax \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438-\u043b\u044f\u043c\u0431\u0434\u0430\u043c\u0438. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c ReconstructChain() \u2014 \u0431\u0435\u0440\u0451\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043b\u044f\u043c\u0431\u0434\u044b \u043d\u0430 e:private static string ReconstructChain(ExpressionSyntax expr, string paramName){    return expr.ToString().Replace($&#187;{paramName}.&#187;, &#171;&#187;);}\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443:History.OrderByDescending(h =&gt; h.Timestamp)    .Select(h =&gt; (OrderStatus?)h.Status)    .FirstOrDefault()\u042d\u0442\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 Where() \u043a\u0430\u043a \u0435\u0441\u0442\u044c \u2014 EF Core \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u0432 SQL-\u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441.\u0410\u0432\u0442\u043e-\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u0413\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u043d\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f. \u041e\u043d \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0435\u0433\u043e \u043f\u043e \u0442\u0438\u043f\u0443 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f Expression:private static CompareOperator InferDefaultOperator(ITypeSymbol returnType){    if (returnType.SpecialType == SpecialType.System_String)        return CompareOperator.Contains;    return CompareOperator.Equal;}string \u2192 Contains (\u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435 \u2014 \u0441\u0430\u043c\u044b\u0439 \u0447\u0430\u0441\u0442\u044b\u0439 \u043a\u0435\u0439\u0441).\u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u2192 Equal.\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 [Compare(&#8230;)]:[Compare(CompareOperator.GreaterThanOrEqual)]public static Expression&lt;Func&lt;Order, decimal?&gt;&gt;? MinAmount { get; } = o =&gt; o.Amount;\u0428\u0430\u0433 4: \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u0434\u0430\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 POCO-\u043a\u043b\u0430\u0441\u0441 \u0438 extension-\u043a\u043b\u0430\u0441\u0441 \u0441 \u043c\u0435\u0442\u043e\u0434\u043e\u043c Apply():\u041a\u043b\u0430\u0441\u0441 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434:public class OrderFilterParams{    public Guid? CustomerId { get; set; }    public string? Description { get; set; }    public decimal? MinAmount { get; set; }    public decimal? MaxAmount { get; set; }    public string? CustomerName { get; set; }    public Guid? CategoryId { get; set; }    \/\/ &#8230; \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-488597","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/488597","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=488597"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/488597\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=488597"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=488597"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=488597"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}