【问题标题】:Select multiple rows in order based on list of Ids with LINQ根据带有 LINQ 的 Id 列表按顺序选择多行
【发布时间】:2017-08-24 20:35:26
【问题描述】:

我有一个 id 列表。如何选择 id 在列表中的所有行并保留顺序?

这就是我所拥有的:

var ids = new int[]{5, 1, 8, 2, 3};
var items = Db.Items.Where(x => ids.Contains(x.Id));

但它不会保留顺序。

我知道我可以根据 id 对项目进行排序,但我想要 O(n) 复杂度。

这个问题类似于:Select multiple records based on list of Id's with linq 但我需要保持行的顺序

【问题讨论】:

  • 为了声明一维 int 数组,您应该使用 var ids = new int[] {5, 1, 8, 2, 3};
  • 此外,此查询已“编译”,因此可能在数据库级别完成。大多数数据库不对订单做出任何保证,除非使用 ORDER BY 语句(或 LINQ 等效语句)。
  • 谢谢。它是在数据库级别完成的,但应该有一种方法来执行我的查询并保持 O(n) 复杂性,因为理论上是可能的。如果没有语法可以得到这个结果,那么这似乎是一个设计缺陷。例如我可以执行 N 个查询并保留顺序,并具有适当的复杂性。但我想在 1 个查询中执行此操作。
  • 不,因为没有 where 的查询也没有保证顺序。如果您要重新导入数据库、修改几行等,则会对获取对象的顺序产生巨大影响。
  • @Sach 通常你是对的,但在这种情况下,LINQ 操作由实体框架专门处理。查询被分析并转换为 SQL 查询。在这种情况下,将 Contains 转换为 Join 操作就足够聪明了。

标签: sql-server performance entity-framework linq


【解决方案1】:

我不太喜欢查询和数据库,但你可以通过使用 C# 技巧很快地做到这一点

var ids = new int[]{5, 1, 8, 2, 3};
var dict = ids.Select((id, i) => new { Index = i, Id = id })
    .ToDictionary(x => x.Id, x => x.Index);
var items = Db.Items
    .Where(x => ids.Contains(x.Id))
    .OrderBy(x => dict[x.Id]);

我不知道它将如何被翻译成数据库查询


我还没有测试过,但这里有一个没有OrderBy 的版本,但空间效率较低(实际上可能更慢):

var ids = new int[]{5, 1, 8, 2, 3};
var temp = Db.Items
.Where(x => ids.Contains(x.Id))
.ToLookup(x => x.Id);

var tempList = new List<IGrouping<int, Item>>();
for(int i = 0; i < ids.Length; i++)
{
    tempList.Add(temp[ids[i]]);
}

var items = tempList.SelectMany(x => x);

还有另一种方法 - 只需进行反向连接:

var ids = new int[]{5, 1, 8, 2, 3};
var items = from id in ids
            join item in Db.Items
            on id equals item.Id
            select item;

这将导致按 id 排序的查询

【讨论】:

  • 这是最好的答案,这也可以很容易地包装到扩展方法中以供重复使用。
  • 看起来不错,但理论上应该有更好的方法而不必使用 OrderBy。
  • @ison 如果你想看看我想出的东西,我刚刚有了一些新的想法
  • @JakubDąbek 新解决方案看起来很棒,并且完全符合我的需要,但它不会执行 N 个查询而不是 1 个吗?我认为从“from id in ids”开始会导致一个简单的不可查询的 LINQ 操作在它被调用而不是数据库的地方执行,但我可能是错的。
  • @ison 我从来没有使用过数据库,所以我无法告诉你它的行为方式。我只有普通 LINQ 的经验。你必须自己测试它
【解决方案2】:

投影到中间类型以保留原始索引、构建联合并对索引进行排序怎么样?

class ItemWithIndex
{
    public int Index { get; set; }
    public Item Item { get; set; }
}

class Item
{
    public int Id { get; set; }
}

int[] ids = { 5, 1, 8, 2, 3 };

IQueryable<ItemWithIndex> query = null;

for(int index = 0; index < ids.Length; index++)
{
    int currentIndex = index;
    int currentId = ids[index];

    IQueryable<ItemWithIndex> next = db.Items
        .Where(i => i.Id == currentId)
        .Select(i => new ItemWithIndex { Index = currentIndex, Item = i });

    query = query == null ? next : query.Concat(next);
}

ItemWithIndex[] items = query
    .OrderBy(i => i.Index)
    .ToArray();

这是生成的查询:

SELECT 
    [UnionAll4].[Id] AS [C1], 
    [UnionAll4].[C1] AS [C2], 
    [UnionAll4].[Id1] AS [C3]
    FROM  (SELECT 
        [Extent1].[Id] AS [Id], 
        @p__linq__1 AS [C1], 
        [Extent1].[Id] AS [Id1]
        FROM [dbo].[Items] AS [Extent1]
        WHERE [Extent1].[Id] = @p__linq__0
    UNION ALL
        SELECT 
        [Extent2].[Id] AS [Id], 
        @p__linq__3 AS [C1], 
        [Extent2].[Id] AS [Id1]
        FROM [dbo].[Items] AS [Extent2]
        WHERE [Extent2].[Id] = @p__linq__2
    UNION ALL
        SELECT 
        [Extent3].[Id] AS [Id], 
        @p__linq__5 AS [C1], 
        [Extent3].[Id] AS [Id1]
        FROM [dbo].[Items] AS [Extent3]
        WHERE [Extent3].[Id] = @p__linq__4
    UNION ALL
        SELECT 
        [Extent4].[Id] AS [Id], 
        @p__linq__7 AS [C1], 
        [Extent4].[Id] AS [Id1]
        FROM [dbo].[Items] AS [Extent4]
        WHERE [Extent4].[Id] = @p__linq__6
    UNION ALL
        SELECT 
        [Extent5].[Id] AS [Id], 
        @p__linq__9 AS [C1], 
        [Extent5].[Id] AS [Id1]
        FROM [dbo].[Items] AS [Extent5]
        WHERE [Extent5].[Id] = @p__linq__8) AS [UnionAll4]

【讨论】:

  • 这仍然进行 N 次查询
  • 我认为它会往返于数据库,所以看起来不错。但问题是您仍然必须对元素进行排序。理想情况下,它们应该从数据库中排序。
  • 实际上,我现在在我面前打开了 SQL Server 分析器,它都在一个(非常丑陋的)查询中运行。
  • 它们确实来自数据库。 OrdeyBy 在查询中生成一个 ORDER BY。
  • 哦,这很有趣。我想知道它是否优于多次往返方法。它看起来有点复杂,但它是一个有趣的解决方案。
【解决方案3】:

我会这么说,

1st,你可以像以前一样得到物品:

var ids = new int[]{5, 1, 8, 2, 3};
var items = Db.Items.Where(x => ids.Contains(x.Id));

然后你可以这样做:

var orderedItems = new int[ids.Length()] // sorry, I'm codign in SO edit, not sure on the syntax
foreach(id in items)
{
var position = Array.IndexOf(items, id)
orderedITems[position] = id;
}

这应该符合您的要求(也可以简化为一行)。

希望对你有帮助

胡安

【讨论】:

    【解决方案4】:

    这是一个可能的解决方案:

    var ids = new int[] {5, 1, 8, 2, 3};
    var items = new List<Item>();
    for (int i = 0; i < ids.Length; i++)
    {
        items.Add(Db.Items.Find(ids[i]));
    }
    

    但是,它执行 N 次查询,所以应该有更好的方法。

    【讨论】:

    • 如果在数据库的 1 次往返中完成,这将是完美的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-28
    • 2019-01-27
    • 2021-03-29
    • 2013-07-31
    • 2020-10-19
    相关资源
    最近更新 更多