是否可以使用“Array.IndexOf”或其他代码派生数据对数据库中的 EF 查询结果进行排序?

Posted

技术标签:

【中文标题】是否可以使用“Array.IndexOf”或其他代码派生数据对数据库中的 EF 查询结果进行排序?【英文标题】:Is is possible to sort EF query results in the database using "Array.IndexOf" or other code-derived data? 【发布时间】:2021-12-04 00:56:10 【问题描述】:

以下代码有效,但需要先从数据库中获取结果:

// ID's by priority (arbitrary yet limited number; eg. 1 to 10 values)
var ids = new []  3, 1, 2 ;

// Should return "item 3" if it exists, else "item 1", else "item 2"..
var firstItemByPriority = context.Items
    .Where(i => ids.Contains(i.Id))
    .ToList() // materialize DB results
    .OrderBy(i => Array.IndexOf(ids, i.Id))
    .FirstOrDefault(); // ie. TOP 1 / LIMIT 1 / First (by ordering)

有没有一种方法可以在生成的数据库查询中中进行排序? (在手动编写 SQL 时,可以通过几种不同的方式构建。)

虽然上面的示例使用Array.IndexOf,但总体目标是按不在数据库中的派生数据进行排序。使用 SQL Server 特定的扩展和/或第三方扩展是公平的。

【问题讨论】:

即使你可以 Array.IndexOf(ids, i.Id)O(n) 这意味着你的整个循环是 O(n*n) - 这太糟糕了。我本来希望有你代表级别的人来了解运行时的复杂性...... SQL 中结果的排序无关紧要——所以在客户端进行排序就可以了。为什么要这样做? 将您的 ids 转换为带有索引的字典(在 O(n) 中),如下所示:var sortOrders = ids.Select( ( v, idx ) => ( v, idx ) ).ToDictionary( t.v, t.idx ); 然后将其作为表值参数传递到您的 Linq 查询中,假设您的 Linq 提供程序支持它(有趣的事实:他们没有),这就是为什么您需要在客户端执行它 - 但使用字典而不是 Array.IndexOf,因为这会给您带来糟糕的性能。 Big-O 描述了系统变得非常大时的行为。如果 N 很小,如问题中所述,O(N^2) 性能可能并不重要(如果您负担得起 DB 查询,排序时间将相对较短),并且基于字典的解决方案可能会执行类似的操作无论如何都要对非常小的 N 进行数组扫描。 一个小型 BenchmarkDotNet 测试,用数组替换 context.Item 以关注 Array.IndexOf() 与字典查找的差异:WithArray 260.5ns,WithSet429.5ns。 【参考方案1】:

没有原始 SQL 查询就无法有效地执行此操作,并且原始 SQL 查询不能与 EF6 中的 LINQ 查询组合,就像它们在 EF Core 中一样。但是用于此的 SQL 查询非常简单。所以是这样的:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;

namespace Ef6Test


    public class Item
    
        public int Id  get; set; 
        public String Name  get; set; 

    


    public class Db : DbContext
    
        public Db() : base("server=localhost;database=ef6Test;integrated security=true")
         
        public DbSet<Item> Items get; set; 
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        
            modelBuilder.Entity<Item>()
                        .ToTable("Items")
                        .Property(i => i.Id)
                        .HasColumnName("Id")
                        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

            base.OnModelCreating(modelBuilder);
        


    

    internal class Program
    

        public static void Main(string[] args)
        

            using (var db = new Db())
            
                db.Database.Log = m => Console.WriteLine(m);

                if (db.Database.Exists())
                    db.Database.Delete();

                db.Database.Create();

                for (int i = 0; i < 1000; i += 2)
                
                    var item = new Item()  Id = i, Name = $"Itemi" ;
                    db.Items.Add(item);
                    if (i % 100 == 0)
                        db.SaveChanges();

                
                db.SaveChanges();

            

            using (var db = new Db())
            
                db.Database.Connection.Open();

                db.Database.ExecuteSqlCommand("create table #ids(position int primary key, id int)");

                var ids = new[]  3, 1, 12, 5, 8, 10, 7;
                var json = System.Text.Json.JsonSerializer.Serialize(ids);

                var pIds = new SqlParameter("@ids", System.Data.SqlDbType.NVarChar, -1);
                pIds.Value = json;

                db.Database.ExecuteSqlCommand("insert into #ids(position,id) select [key], [value] from openjson(@ids)", pIds);

                var sql = @"
select top (@count) i.*
from Items i
join #ids ids
  on i.Id=ids.Id
order by ids.position
";
                var pCount = new SqlParameter("@count", 3);
                var results = db.Database.SqlQuery<Item>(sql,pCount).ToList();
                
                foreach (var item in results)
                
                    Console.WriteLine(item.Id);
                
              
            
            Console.WriteLine("complete");
         
  
        
    

输出

12
8
10
complete

【讨论】:

嗯,在 nvarchar 参数与 TVP 中传递 JSON 的性能如何?另外,JSON nvarchar 的最大长度不是 4000 个字符吗? 出奇的好,不,它是 nvarchar(max)。

以上是关于是否可以使用“Array.IndexOf”或其他代码派生数据对数据库中的 EF 查询结果进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

c# Array.FindAllIndexOf which FindAll IndexOf

js在数组中查找是否存在某一个数值

Javascript 二维数组 indexOf

System.Array.IndexOf 分配内存

Javascript:array.indexOf()没有返回正确的元素索引[重复]

JavaScript Array.indexOf