Linq中的自定义OrderBy具有单链接的实体列表

时间:2018-03-14 21:57:24

标签: c# performance entity-framework linq

我正在尝试提供一种有效的解决方案,以便能够查询实体列表并正确排序。我在SQL DB模式中创建了一个单链表类型结构。我使用GUID作为我的ID,但为了简单起见,我将在这里使用int。我可以通过在DB上安装SortOrder列来轻松解决此问题,但由于其他要求,我必须实现此表。

我有一个类似于以下实体模型的表结构:

public class Record
{
    public int ID;
    public string Name;
    public int? ChildID;  //References the next record
}

我最初的想法是创建一个如下所示的部分类:

public partial class Record
{
    public int SortOrder
    {
        get
        {
           //query table and loop through the entire list building it from
           //the bottom and keeping count of the integer sort order and return 
           //the one specific to this record
        }
    }
}

但是,每次查询整个列表并迭代查找SortOrder似乎效率非常低。还有什么我可以像定制的OrderBy函数或任何东西一样利用?我正在尝试按迭代构建列表时创建的顺序排序。例如,ChildID = null的记录是列表中的最后一个记录,因为它没有子记录。我将从该记录开始,然后获取它上面的下一个记录,它引用前一个作为其ChildID,直到列表中没有其他ID引用,这应该是列表完成并正确排序。没有两个记录具有相同的ChildID。

如果我在列表中有以下3条记录,

ID = 3,  Name = "Apple",  ChildID = 6,
ID = 54, Name = "Orange", ChildID = 3,
ID = 6,  Name = "Banana", ChildID = null

然后我希望按顺序获得Orange,Apple,Banana。

3 个答案:

答案 0 :(得分:1)

一种方法是编写一个按排序顺序返回列表的方法。您首先会找到ChildId == null的记录,将其添加到结果列表,然后继续搜索item.ChildId == previousItem.Id的项目,然后将它们插入列表的开头:

private static IEnumerable<Record> OrderRecords(IReadOnlyCollection<Record> records)
{
    // "Exit fast" checks
    if (records == null) return null;
    if (records.Count < 2) return records.ToList();

    // Get last record and add it to our results
    Record currentRecord = records.Single(r => r.ChildID == null);
    var results = new List<Record> {currentRecord};

    // Keep getting the parent reference to the previous record 
    // and insert it at the beginning of the results list
    while ((currentRecord = records.SingleOrDefault(r => 
        r.ChildID == currentRecord.ID)) != null)
    {
        results.Insert(0, currentRecord);
    }

    return results;
}

在使用中,这看起来像是:

private static void Main()
{
    var records = new List<Record>
    {
        new Record {ID = 3, Name = "Apple", ChildID = 6},
        new Record {ID = 54, Name = "Orange", ChildID = 3},
        new Record {ID = 6, Name = "Banana", ChildID = null}
    };

    var sortedRecords = OrderRecords(records);

    Console.WriteLine(string.Join(", ", sortedRecords.Select(r => r.Name)));

    Console.Write("\nPress any key to exit...");
    Console.ReadKey();
}

<强>输出

enter image description here

答案 1 :(得分:1)

鉴于记录ID顺序是随机的,并假设您订购的List已完成,或者如果您必须扫描整个表以订购,则不会耗尽内存/时间列表,我认为您可以做的最好的事情是计算Record的深度并缓存结果:

我使用List作为表,但如果您要订购的列表不完整,则可以使用该表:

public partial class Record {
    static Dictionary<int, int> depth = new Dictionary<int, int>();
    public int Depth(List<Record> dbTable) {
        int ans = 0;

        var working = new Queue<int>();
        var cur = this;
        do {
            if (depth.TryGetValue(cur.ID, out var curDepth)) {
                ans += curDepth;
                break;
            }
            else {
                working.Enqueue(cur.ID);
                cur = dbTable.FirstOrDefault(r => r.ChildID == cur.ID);
                if (cur != null)
                    ++ans;
            }
        } while (cur != null);

        var workAns = ans;
        while (working.Count > 0) {
            var workingID = working.Dequeue();
            depth.Add(workingID, workAns);
            --workAns;
        }

        return ans;
    }
}

更新:我重新编写代码以使用特定队列;我的第一个版本是递归的,这是直截了当的,但有可能溢出堆栈,我的第二个版本没有缓存中间结果时跟随链表不是很有效。使用中间ID的队列确保我只跟踪一次特定的链深度。

现在你有Depth方法,排序很简单:

var ans = work.OrderBy(w => w.Depth(work));

答案 2 :(得分:1)

此任务的最佳算法是按Dictionary准备Record的快速查找数据结构(如ChildID)。然后,可以从ChildID = null开始向后生成有序结果,并使用记录ID查找上一条记录。

由于哈希查找时间复杂度为O(1),因此算法的时间复杂度为线性O(N) - 最快。

以下是实施:

static Record[] Ordered(IEnumerable<Record> records)
{
    var recordByNextId = records.ToDictionary(e => e.ChildID.Wrap());
    var result = new Record[recordByNextId.Count];
    int? nextId = null;
    for (int i = result.Length - 1; i >=0; i--)
        nextId = (result[i] = recordByNextId[nextId]).ID;
    return result;
}

e.ChildID.Wrap()自定义扩展方法的说明。我希望我只能使用e.ChildID,但是BCL Dictionary类会引发null密钥的恼人异常。为了克服这个限制,我使用一个简单的包装器struct和“流利的”帮助器:

public struct ValueWrapper<T> : IEquatable<ValueWrapper<T>>
{
    public readonly T Value;
    public ValueWrapper(T value) => Value = value;
    public bool Equals(ValueWrapper<T> other) => EqualityComparer<T>.Default.Equals(Value, other.Value);
    public override bool Equals(object obj) => obj is ValueWrapper<T> other && Equals(other);
    public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Value);
    public static implicit operator ValueWrapper<T>(T x) => new ValueWrapper<T>(x);
    public static implicit operator T(ValueWrapper<T> x) => x.Value;
}

public static class ValueWrapper
{
    public static ValueWrapper<T> Wrap<T>(this T value) => new ValueWrapper<T>(value);
}