{"id":1136,"date":"2025-05-27T14:47:16","date_gmt":"2025-05-27T06:47:16","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1136"},"modified":"2025-05-27T14:47:16","modified_gmt":"2025-05-27T06:47:16","slug":"ultimate-asp-net-core-web-api-19-sorting","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1136","title":{"rendered":"Ultimate ASP.NET Core Web API 19 SORTING"},"content":{"rendered":"<p>19 SORTING<br \/>\n19 \u6392\u5e8f<\/p>\n<p>In this chapter, we\u2019re going to talk about sorting in ASP.NET Core Web API. Sorting is a commonly used mechanism that every API should implement. Implementing it in ASP.NET Core is not difficult due to the flexibility of LINQ and good integration with EF Core.\u200c<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba ASP.NET Core Web API \u4e2d\u7684\u6392\u5e8f\u3002\u6392\u5e8f\u662f\u6bcf\u4e2a API \u90fd\u5e94\u8be5\u5b9e\u73b0\u7684\u5e38\u7528\u673a\u5236\u3002\u7531\u4e8e LINQ \u7684\u7075\u6d3b\u6027\u4ee5\u53ca\u4e0e EF Core \u7684\u826f\u597d\u96c6\u6210\uff0c\u5728 ASP.NET Core \u4e2d\u5b9e\u73b0\u5b83\u5e76\u4e0d\u56f0\u96be\u3002<\/p>\n<p>So, let\u2019s talk a bit about sorting.<br \/>\n\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u8c08\u8c08\u6392\u5e8f\u3002<\/p>\n<h2>19.1 What is Sorting?<\/h2>\n<p>19.1 \u4ec0\u4e48\u662f\u6392\u5e8f\uff1f<\/p>\n<p>Sorting, in this case, refers to ordering our results in a preferred way using our query string parameters. We are not talking about sorting algorithms nor are we going into the how\u2019s of implementing a sorting algorithm.\u200c<br \/>\n\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6392\u5e8f\u662f\u6307\u4f7f\u7528\u6211\u4eec\u7684\u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\u4ee5\u9996\u9009\u65b9\u5f0f\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\u3002\u6211\u4eec\u4e0d\u662f\u5728\u8c08\u8bba\u6392\u5e8f\u7b97\u6cd5\uff0c\u4e5f\u4e0d\u6253\u7b97\u8ba8\u8bba\u5982\u4f55\u5b9e\u73b0\u6392\u5e8f\u7b97\u6cd5\u3002<\/p>\n<p>What we\u2019re interested in, however, is how do we make our API sort our results the way we want it to.<br \/>\n\u7136\u800c\uff0c\u6211\u4eec\u611f\u5174\u8da3\u7684\u662f\u6211\u4eec\u5982\u4f55\u8ba9\u6211\u4eec\u7684 API \u6309\u7167\u6211\u4eec\u60f3\u8981\u7684\u65b9\u5f0f\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\u3002<\/p>\n<p>Let\u2019s say we want our API to sort employees by their name in ascending order, and then by their age.<br \/>\n\u5047\u8bbe\u6211\u4eec\u5e0c\u671b API \u5148\u6309\u5458\u5de5\u59d3\u540d\u5347\u5e8f\u6392\u5e8f\uff0c\u7136\u540e\u518d\u6309\u5e74\u9f84\u5bf9\u5458\u5de5\u8fdb\u884c\u6392\u5e8f\u3002<\/p>\n<p>To do that, our API call needs to look something like this:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u7684 API \u8c03\u7528\u9700\u8981\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/companyId\/employees?orderBy=name,age\">https:\/\/localhost:5001\/api\/companies\/companyId\/employees?orderBy=name,age<\/a> desc<\/p>\n<p>Our API needs to consider all the parameters and sort our results accordingly. In our case, this means sorting results by their name; then, if there are employees with the same name, sorting them by the age property.<br \/>\n\u6211\u4eec\u7684 API \u9700\u8981\u8003\u8651\u6240\u6709\u53c2\u6570\u5e76\u76f8\u5e94\u5730\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\u3002\u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\uff0c\u8fd9\u610f\u5473\u7740\u6309\u540d\u79f0\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f;\u7136\u540e\uff0c\u5982\u679c\u5b58\u5728\u540c\u540d\u7684\u5458\u5de5\uff0c\u5219\u6309 age \u5c5e\u6027\u5bf9\u4ed6\u4eec\u8fdb\u884c\u6392\u5e8f\u3002<\/p>\n<p>So, these are our employees for the IT_Solutions Ltd company:<br \/>\n\u90a3\u4e48\uff0c\u8fd9\u4e9b\u662f\u6211\u4eec IT_Solutions Ltd \u516c\u53f8\u7684\u5458\u5de5\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1901.jpg\" alt=\"alt text\" \/><\/p>\n<p>For the sake of demonstrating this example (sorting by name and then by age), we are going to add one more Jana McLeaf to our database with the age of 27. You can add whatever you want to test the results:<br \/>\n\u4e3a\u4e86\u6f14\u793a\u6b64\u793a\u4f8b\uff08\u5148\u6309\u59d3\u540d\u6392\u5e8f\uff0c\u7136\u540e\u6309\u5e74\u9f84\u6392\u5e8f\uff09\uff0c\u6211\u4eec\u5c06\u5411\u6570\u636e\u5e93\u4e2d\u518d\u6dfb\u52a0\u4e00\u4e2a\u5e74\u9f84\u4e3a 27 \u7684 Jana McLeaf\u3002\u60a8\u53ef\u4ee5\u6dfb\u52a0\u4efb\u4f55\u60a8\u60f3\u8981\u6d4b\u8bd5\u7ed3\u679c\u7684\u5185\u5bb9\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1902.jpg\" alt=\"alt text\" \/><\/p>\n<p>Great, now we have the required data to test our functionality properly.<br \/>\n\u592a\u597d\u4e86\uff0c\u73b0\u5728\u6211\u4eec\u6709\u4e86\u6b63\u786e\u6d4b\u8bd5\u6211\u4eec\u7684\u529f\u80fd\u6240\u9700\u7684\u6570\u636e\u3002<\/p>\n<p>And of course, like with all other functionalities we have implemented so far (paging, filtering, and searching), we need to implement this to work well with everything else. We should be able to get the paginated, filtered, and sorted data, for example.<br \/>\n\u5f53\u7136\uff0c\u5c31\u50cf\u6211\u4eec\u5230\u76ee\u524d\u4e3a\u6b62\u5b9e\u73b0\u7684\u6240\u6709\u5176\u4ed6\u529f\u80fd\uff08\u5206\u9875\u3001\u8fc7\u6ee4\u548c\u641c\u7d22\uff09\u4e00\u6837\uff0c\u6211\u4eec\u9700\u8981\u5b9e\u73b0\u5b83\u624d\u80fd\u4e0e\u5176\u4ed6\u6240\u6709\u529f\u80fd\u5f88\u597d\u5730\u534f\u540c\u5de5\u4f5c\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u5e94\u8be5\u80fd\u591f\u83b7\u53d6\u5206\u9875\u3001\u8fc7\u6ee4\u548c\u6392\u5e8f\u7684\u6570\u636e\u3002<\/p>\n<p>Let\u2019s see one way to go around implementing this.<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5b9e\u73b0\u5b83\u7684\u65b9\u6cd5\u3002<\/p>\n<h2>19.2 How to Implement Sorting in ASP.NET Core Web API<\/h2>\n<p>19.2 \u5982\u4f55\u5728 ASP.NET Core Web API \u4e2d\u5b9e\u73b0\u6392\u5e8f<\/p>\n<p>As with everything else so far, first, we need to extend our RequestParameters class to be able to send requests with the orderBy clause in them:\u200c<br \/>\n\u4e0e\u5230\u76ee\u524d\u4e3a\u6b62\u7684\u5176\u4ed6\u6240\u6709\u5185\u5bb9\u4e00\u6837\uff0c\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u6269\u5c55 RequestParameters \u7c7b\uff0c\u4ee5\u4fbf\u80fd\u591f\u53d1\u9001\u5305\u542b orderBy \u5b50\u53e5\u7684\u8bf7\u6c42\uff1a<\/p>\n<pre><code>public class RequestParameters { const int maxPageSize = 50; public int PageNumber { get; set; } = 1; private int _pageSize = 10; public int PageSize { get { return _pageSize; } set { _pageSize = (value &gt; maxPageSize) ? maxPageSize : value; } } public string? OrderBy { get; set; } }<\/code><\/pre>\n<p>As you can see, the only thing we\u2019ve added is the OrderBy property and we added it to the RequestParameters class because we can reuse it for other entities. We want to sort our results by name, even if it hasn\u2019t been stated explicitly in the request.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u6dfb\u52a0\u7684\u552f\u4e00\u5185\u5bb9\u662f OrderBy \u5c5e\u6027\uff0c\u5e76\u5c06\u5176\u6dfb\u52a0\u5230 RequestParameters \u7c7b\u4e2d\uff0c\u56e0\u4e3a\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u91cd\u65b0\u7528\u4e8e\u5176\u4ed6\u5b9e\u4f53\u3002\u6211\u4eec\u5e0c\u671b\u6309\u540d\u79f0\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\uff0c\u5373\u4f7f\u8bf7\u6c42\u4e2d\u6ca1\u6709\u660e\u786e\u8bf4\u660e\u3002<\/p>\n<p>That said, let\u2019s modify the EmployeeParameters class to enable the default sorting condition for Employee if none was stated:<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 EmployeeParameters \u7c7b\uff0c\u4ee5\u542f\u7528 Employee \u7684\u9ed8\u8ba4\u6392\u5e8f\u6761\u4ef6\uff08\u5982\u679c\u672a\u8bf4\u660e\uff09\uff1a<\/p>\n<pre><code>public class EmployeeParameters : RequestParameters { public EmployeeParameters() =&gt; OrderBy = &quot;name&quot;; public uint MinAge { get; set; } public uint MaxAge { get; set; } = int.MaxValue; public bool ValidAgeRange =&gt; MaxAge &gt; MinAge; public string? SearchTerm { get; set; } }<\/code><\/pre>\n<p>Next, we\u2019re going to dive right into the implementation of our sorting mechanism, or rather, our ordering mechanism.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u6df1\u5165\u7814\u7a76\u6392\u5e8f\u673a\u5236\u7684\u5b9e\u73b0\uff0c\u6216\u8005\u66f4\u786e\u5207\u5730\u8bf4\uff0c\u6211\u4eec\u7684\u6392\u5e8f\u673a\u5236\u3002<\/p>\n<p>One thing to note is that we\u2019ll be using the System.Linq.Dynamic.Core NuGet package to dynamically create our OrderBy query on the fly. So, feel free to install it in the Repository project and add a using directive in the RepositoryEmployeeExtensions class:<br \/>\n\u9700\u8981\u6ce8\u610f\u7684\u4e00\u70b9\u662f\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 System.Linq.Dynamic.Core NuGet \u5305\u52a8\u6001\u521b\u5efa\u52a8\u6001 OrderBy \u67e5\u8be2\u3002\u56e0\u6b64\uff0c\u8bf7\u968f\u610f\u5c06\u5176\u5b89\u88c5\u5728 Repository \u9879\u76ee\u4e2d\uff0c\u5e76\u5728 RepositoryEmployeeExtensions \u7c7b\u4e2d\u6dfb\u52a0 using \u6307\u4ee4\uff1a<\/p>\n<pre><code>using System.Linq.Dynamic.Core;<\/code><\/pre>\n<p>Now, we can add the new extension method Sort in our RepositoryEmployeeExtensions class:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 RepositoryEmployeeExtensions \u7c7b\u4e2d\u6dfb\u52a0\u65b0\u7684\u6269\u5c55\u65b9\u6cd5 Sort\uff1a<\/p>\n<pre><code>public static IQueryable&lt;Employee&gt; Sort(this IQueryable&lt;Employee&gt; employees, string orderByQueryString) { if (string.IsNullOrWhiteSpace(orderByQueryString)) return employees.OrderBy(e =&gt; e.Name); var orderParams = orderByQueryString.Trim().Split(&#039;,&#039;); var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public | BindingFlags.Instance); var orderQueryBuilder = new StringBuilder(); foreach (var param in orderParams) { if (string.IsNullOrWhiteSpace(param)) continue; var propertyFromQueryName = param.Split(&quot; &quot;)[0]; var objectProperty = propertyInfos.FirstOrDefault(pi =&gt; pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase)); if (objectProperty == null) continue; var direction = param.EndsWith(&quot; desc&quot;) ? &quot;descending&quot; : &quot;ascending&quot;; orderQueryBuilder.Append($&quot;{objectProperty.Name.ToString()} {direction}, &quot;); } var orderQuery = orderQueryBuilder.ToString().TrimEnd(&#039;,&#039;, &#039; &#039;); if (string.IsNullOrWhiteSpace(orderQuery)) return employees.OrderBy(e =&gt; e.Name); return employees.OrderBy(orderQuery); }<\/code><\/pre>\n<p>Okay, there are a lot of things going on here, so let\u2019s take it step by step and see what exactly we've done.<br \/>\n\u597d\u4e86\uff0c\u8fd9\u91cc\u53d1\u751f\u4e86\u5f88\u591a\u4e8b\u60c5\uff0c\u6240\u4ee5\u8ba9\u6211\u4eec\u4e00\u6b65\u4e00\u6b65\u6765\uff0c\u770b\u770b\u6211\u4eec\u5230\u5e95\u505a\u4e86\u4ec0\u4e48\u3002<\/p>\n<h2>19.3 Implementation \u2013 Step by Step<\/h2>\n<p>19.3 \u5b9e\u65bd \u2013 \u5206\u6b65<\/p>\n<p>First, let start with the method definition. It has two arguments \u2014 one for the list of employees as IQueryable<Employee> and the other for the ordering query. If we send a request like this one:<br \/>\n\u9996\u5148\uff0c\u8ba9\u6211\u4eec\u4ece\u65b9\u6cd5\u5b9a\u4e49\u5f00\u59cb\u3002\u5b83\u6709\u4e24\u4e2a\u53c2\u6570 \u2014 \u4e00\u4e2a\u7528\u4e8e IQueryable \u7684\u5458\u5de5\u5217\u8868\uff0c\u53e6\u4e00\u4e2a\u7528\u4e8e\u6392\u5e8f\u67e5\u8be2\u3002\u5982\u679c\u6211\u4eec\u53d1\u9001\u5982\u4e0b\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/companyId\/employees?or\">https:\/\/localhost:5001\/api\/companies\/companyId\/employees?or<\/a> derBy=name,age desc, <\/p>\n<p>our orderByQueryString will be name,age desc.\u200c<br \/>\n\u6211\u4eec\u7684 orderByQueryString \u5c06\u662f name\uff0cage desc\u3002<\/p>\n<p>We begin by executing some basic check against the orderByQueryString. If it is null or empty, we just return the same collection ordered by name.<br \/>\n\u6211\u4eec\u9996\u5148\u5bf9 orderByQueryString \u6267\u884c\u4e00\u4e9b\u57fa\u672c\u68c0\u67e5\u3002\u5982\u679c\u4e3a null \u6216\u4e3a\u7a7a\uff0c\u6211\u4eec\u53ea\u8fd4\u56de\u6309\u540d\u79f0\u6392\u5e8f\u7684\u76f8\u540c\u96c6\u5408\u3002<\/p>\n<pre><code>if (string.IsNullOrWhiteSpace(orderByQueryString)) \n    return employees.OrderBy(e =&gt; e.Name);<\/code><\/pre>\n<p>Next, we are splitting our query string to get the individual fields:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u62c6\u5206\u67e5\u8be2\u5b57\u7b26\u4e32\u4ee5\u83b7\u53d6\u5404\u4e2a\u5b57\u6bb5\uff1a<\/p>\n<pre><code>var orderParams = orderByQueryString.Trim().Split(&#039;,&#039;);<\/code><\/pre>\n<p>We\u2019re also using a bit of reflection to prepare the list of PropertyInfo objects that represent the properties of our Employee class. We need them to be able to check if the field received through the query string exists in the Employee class:<br \/>\n\u6211\u4eec\u8fd8\u4f7f\u7528\u4e86\u4e00\u4e9b\u53cd\u5c04\u6765\u51c6\u5907\u8868\u793a Employee \u7c7b\u5c5e\u6027\u7684 PropertyInfo \u5bf9\u8c61\u5217\u8868\u3002\u6211\u4eec\u9700\u8981\u5b83\u4eec\u80fd\u591f\u68c0\u67e5\u901a\u8fc7\u67e5\u8be2\u5b57\u7b26\u4e32\u63a5\u6536\u7684\u5b57\u6bb5\u662f\u5426\u5b58\u5728\u4e8e Employee \u7c7b\u4e2d\uff1a<\/p>\n<pre><code>var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public | BindingFlags.Instance);<\/code><\/pre>\n<p>That prepared, we can actually run through all the parameters and check for their existence:<br \/>\n\u51c6\u5907\u597d\u4e86\uff0c\u6211\u4eec\u5b9e\u9645\u4e0a\u53ef\u4ee5\u904d\u5386\u6240\u6709\u53c2\u6570\u5e76\u68c0\u67e5\u5b83\u4eec\u662f\u5426\u5b58\u5728\uff1a<\/p>\n<pre><code>if (string.IsNullOrWhiteSpace(param)) continue; var propertyFromQueryName = param.Split(&quot; &quot;)[0]; var objectProperty = propertyInfos.FirstOrDefault(pi =&gt; pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));<\/code><\/pre>\n<p>If we don\u2019t find such a property, we skip the step in the foreach loop and go to the next parameter in the list:<br \/>\n\u5982\u679c\u627e\u4e0d\u5230\u8fd9\u6837\u7684\u5c5e\u6027\uff0c\u6211\u4eec\u5c06\u8df3\u8fc7 foreach \u5faa\u73af\u4e2d\u7684\u6b65\u9aa4\uff0c\u5e76\u8f6c\u5230\u5217\u8868\u4e2d\u7684\u4e0b\u4e00\u4e2a\u53c2\u6570\uff1a<\/p>\n<pre><code>if (objectProperty == null) \n    continue;<\/code><\/pre>\n<p>If we do find the property, we return it and additionally check if our parameter contains \u201cdesc\u201d at the end of the string. We use that to decide how we should order our property:<br \/>\n\u5982\u679c\u6211\u4eec\u627e\u5230\u4e86\u8be5\u5c5e\u6027\uff0c\u5219\u8fd4\u56de\u8be5\u5c5e\u6027\uff0c\u5e76\u53e6\u5916\u68c0\u67e5\u6211\u4eec\u7684\u53c2\u6570\u662f\u5426\u5728\u5b57\u7b26\u4e32\u672b\u5c3e\u5305\u542b \u201cdesc\u201d\u3002\u6211\u4eec\u4f7f\u7528\u5b83\u6765\u51b3\u5b9a\u6211\u4eec\u5e94\u8be5\u5982\u4f55\u6392\u5e8f\u6211\u4eec\u7684\u8d22\u4ea7\uff1a<\/p>\n<pre><code>var direction = param.EndsWith(&quot; desc&quot;) ? &quot;descending&quot; : &quot;ascending&quot;;<\/code><\/pre>\n<p>We use the StringBuilder to build our query with each loop:<br \/>\n\u6211\u4eec\u4f7f\u7528 StringBuilder \u6784\u5efa\u5305\u542b\u6bcf\u4e2a\u5faa\u73af\u7684\u67e5\u8be2\uff1a<\/p>\n<pre><code>orderQueryBuilder.Append($&quot;{objectProperty.Name.ToString()} {direction}, &quot;);<\/code><\/pre>\n<p>Now that we\u2019ve looped through all the fields, we are just removing excess commas and doing one last check to see if our query indeed has something in it:<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u904d\u5386\u4e86\u6240\u6709\u5b57\u6bb5\uff0c\u6211\u4eec\u53ea\u662f\u5220\u9664\u591a\u4f59\u7684\u9017\u53f7\u5e76\u8fdb\u884c\u6700\u540e\u4e00\u6b21\u68c0\u67e5\uff0c\u770b\u770b\u6211\u4eec\u7684\u67e5\u8be2\u662f\u5426\u786e\u5b9e\u5305\u542b\u67d0\u4e9b\u5185\u5bb9\uff1a<\/p>\n<pre><code>var orderQuery = orderQueryBuilder.ToString().TrimEnd(&#039;,&#039;, &#039; &#039;); if (string.IsNullOrWhiteSpace(orderQuery)) return employees.OrderBy(e =&gt; e.Name);<\/code><\/pre>\n<p>Finally, we can order our query:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5bf9\u67e5\u8be2\u8fdb\u884c\u6392\u5e8f\uff1a<\/p>\n<pre><code>return employees.OrderBy(orderQuery);<\/code><\/pre>\n<p>At this point, the orderQuery variable should contain the \u201cName ascending, DateOfBirth descending\u201d string. That means it will order our results first by Name in ascending order, and then by DateOfBirth in descending order.<br \/>\n\u6b64\u65f6\uff0corderQuery \u53d8\u91cf\u5e94\u5305\u542b\u201cName ascending\uff0c DateOfBirth descending\u201d\u5b57\u7b26\u4e32\u3002\u8fd9\u610f\u5473\u7740\u5b83\u5c06\u9996\u5148\u6309 Name \u5347\u5e8f\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\uff0c\u7136\u540e\u6309 DateOfBirth \u964d\u5e8f\u6392\u5e8f\u3002<\/p>\n<p>The standard LINQ query for this would be:<br \/>\n\u5bf9\u6b64\u7684\u6807\u51c6 LINQ \u67e5\u8be2\u4e3a\uff1a<\/p>\n<pre><code>employees.OrderBy(e =&gt; e.Name).ThenByDescending(o =&gt; o.Age);<\/code><\/pre>\n<p>This is a neat little trick to form a query when you don\u2019t know in advance how you should sort.<br \/>\n\u5f53\u60a8\u4e8b\u5148\u4e0d\u77e5\u9053\u5e94\u8be5\u5982\u4f55\u6392\u5e8f\u65f6\uff0c\u8fd9\u662f\u4e00\u4e2a\u5de7\u5999\u7684\u5c0f\u6280\u5de7\u6765\u5f62\u6210\u67e5\u8be2\u3002<\/p>\n<p>Once we have done this, all we have to do is to modify the GetEmployeesAsync repository method:<br \/>\n\u5b8c\u6210\u6b64\u4f5c\u540e\uff0c\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u4fee\u6539 GetEmployeesAsync \u5b58\u50a8\u5e93\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task&lt;PagedList&lt;Employee&gt;&gt; GetEmployeesAsync(Guid companyId, EmployeeParameters employeeParameters, bool trackChanges) { var employees = await FindByCondition(e =&gt; e.CompanyId.Equals(companyId), trackChanges) .FilterEmployees(employeeParameters.MinAge, employeeParameters.MaxAge).Search(employeeParameters.SearchTerm) .Sort(employeeParameters.OrderBy) .ToListAsync(); return PagedList&lt;Employee&gt; .ToPagedList(employees, employeeParameters.PageNumber, employeeParameters.PageSize); }\n<\/code><\/pre>\n<p>And that\u2019s it! We can test this functionality now.<br \/>\n\u5c31\u662f\u8fd9\u6837\uff01\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u6d4b\u8bd5\u6b64\u529f\u80fd\u3002<\/p>\n<h2>19.4 Testing Our Implementation<\/h2>\n<p>19.4 \u6d4b\u8bd5\u6211\u4eec\u7684\u5b9e\u73b0<\/p>\n<p>First, let\u2019s try out the query we\u2019ve been using as an example:\u200c<br \/>\n\u9996\u5148\uff0c\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4e00\u4e0b\u6211\u4eec\u4e00\u76f4\u7528\u4f5c\u793a\u4f8b\u7684\u67e5\u8be2\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78<\/a>- 2D54A9991870\/employees?orderBy=name,age desc<\/p>\n<p>And this is the result:<br \/>\n\u7ed3\u679c\u5982\u4e0b\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1903.jpg\" alt=\"alt text\" \/><\/p>\n<p>We can see that this list is sorted by Name ascending. Since we have two Jana\u2019s, they were sorted by Age descending.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\uff0c\u8fd9\u4e2a\u5217\u8868\u662f\u6309 Name \u5347\u5e8f\u6392\u5e8f\u7684\u3002\u7531\u4e8e\u6211\u4eec\u6709\u4e24\u4e2a Jana\uff0c\u56e0\u6b64\u5b83\u4eec\u6309 Age \u964d\u5e8f\u6392\u5e8f\u3002<\/p>\n<p>We have prepared additional requests which you can use to test this functionality with Postman. So, feel free to do it.<br \/>\n\u6211\u4eec\u51c6\u5907\u4e86\u5176\u4ed6\u8bf7\u6c42\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e9b\u8bf7\u6c42\u6765\u901a\u8fc7 Postman \u6d4b\u8bd5\u6b64\u529f\u80fd\u3002\u6240\u4ee5\uff0c\u8bf7\u968f\u610f\u53bb\u505a\u3002<\/p>\n<h2>19.5 Improving the Sorting Functionality<\/h2>\n<p>19.5 \u6539\u8fdb\u6392\u5e8f\u529f\u80fd<\/p>\n<p>Right now, sorting only works with the Employee entity, but what about the Company? It is obvious that we have to change something in our implementation if we don\u2019t want to repeat our code while implementing sorting for the Company entity.\u200c<br \/>\n\u76ee\u524d\uff0c\u6392\u5e8f\u4ec5\u9002\u7528\u4e8e Employee \u5b9e\u4f53\uff0c\u4f46 Company \u5462\uff1f\u5f88\u660e\u663e\uff0c\u5982\u679c\u6211\u4eec\u4e0d\u60f3\u5728\u4e3a Company \u5b9e\u4f53\u5b9e\u73b0\u6392\u5e8f\u65f6\u91cd\u590d\u6211\u4eec\u7684\u4ee3\u7801\uff0c\u6211\u4eec\u5fc5\u987b\u5728\u5b9e\u73b0\u4e2d\u66f4\u6539\u67d0\u4e9b\u5185\u5bb9\u3002<\/p>\n<p>That said, let\u2019s modify the Sort extension method:<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 Sort \u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static IQueryable&lt;Employee&gt; Sort(this IQueryable&lt;Employee&gt; employees, string orderByQueryString) { if (string.IsNullOrWhiteSpace(orderByQueryString)) return employees.OrderBy(e =&gt; e.Name); var orderQuery = OrderQueryBuilder.CreateOrderQuery&lt;Employee&gt;(orderByQueryString); if (string.IsNullOrWhiteSpace(orderQuery)) return employees.OrderBy(e =&gt; e.Name); return employees.OrderBy(orderQuery); }<\/code><\/pre>\n<p>So, we are extracting a logic that can be reused in the CreateOrderQuery<T> method. But of course, we have to create that method.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u6b63\u5728\u63d0\u53d6\u53ef\u5728 CreateOrderQuery \u65b9\u6cd5\u4e2d\u91cd\u590d\u4f7f\u7528\u7684\u903b\u8f91\u3002\u4f46\u5f53\u7136\uff0c\u6211\u4eec\u5fc5\u987b\u521b\u5efa\u8be5\u65b9\u6cd5\u3002<\/p>\n<p>Let\u2019s create a Utility folder in the Extensions folder with the new class OrderQueryBuilder:<br \/>\n\u8ba9\u6211\u4eec\u5728 Extensions \u6587\u4ef6\u5939\u4e2d\u4f7f\u7528\u65b0\u7c7b OrderQueryBuilder \u521b\u5efa\u4e00\u4e2a Utility \u6587\u4ef6\u5939\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1904.jpg\" alt=\"alt text\" \/><\/p>\n<p>Now, let\u2019s modify that class:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u4fee\u6539\u8be5\u7c7b\uff1a<\/p>\n<pre><code>public static class OrderQueryBuilder { public static string CreateOrderQuery&lt;T&gt;(string orderByQueryString) { var orderParams = orderByQueryString.Trim().Split(&#039;,&#039;); var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); var orderQueryBuilder = new StringBuilder();foreach (var param in orderParams) { if (string.IsNullOrWhiteSpace(param)) continue; var propertyFromQueryName = param.Split(&quot; &quot;)[0]; var objectProperty = propertyInfos.FirstOrDefault(pi =&gt; pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase)); if (objectProperty == null) continue; var direction = param.EndsWith(&quot; desc&quot;) ? &quot;descending&quot; : &quot;ascending&quot;; orderQueryBuilder.Append($&quot;{objectProperty.Name.ToString()} {direction}, &quot;); } var orderQuery = orderQueryBuilder.ToString().TrimEnd(&#039;,&#039;, &#039; &#039;); return orderQuery; } }\n<\/code><\/pre>\n<p>And there we go. Not too many changes, but we did a great job here. You can test this solution with the prepared requests in Postman and you'll get the same result for sure:<br \/>\n\u597d\u4e86\u3002\u6ca1\u6709\u592a\u591a\u7684\u53d8\u5316\uff0c\u4f46\u6211\u4eec\u5728\u8fd9\u91cc\u505a\u5f97\u5f88\u597d\u3002\u60a8\u53ef\u4ee5\u5728 Postman \u4e2d\u4f7f\u7528\u51c6\u5907\u597d\u7684\u8bf7\u6c42\u6d4b\u8bd5\u6b64\u89e3\u51b3\u65b9\u6848\uff0c\u60a8\u80af\u5b9a\u4f1a\u5f97\u5230\u76f8\u540c\u7684\u7ed3\u679c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1905.jpg\" alt=\"alt text\" \/><\/p>\n<p>But now, this functionality is reusable.<br \/>\n\u4f46\u73b0\u5728\uff0c\u6b64\u529f\u80fd\u662f\u53ef\u91cd\u7528\u7684\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>19 SORTING 19 \u6392\u5e8f In this chapter, we\u2019re going to talk about sorting in ASP.NET Core Web API. Sorting is a commonly used mechanism that every API should implement. Implementing it in ASP.NET Core is not difficult due to the flexibility of LINQ and good integration with EF Core.\u200c \u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba ASP.NET Core Web API \u4e2d\u7684\u6392\u5e8f\u3002\u6392\u5e8f\u662f\u6bcf\u4e2a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1136","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1136","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1136"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1136\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1136"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1136"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1136"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}