您在 SQL 方面做了很多工作,但您可以在纯 EF 中做同样的事情。
下次为您提供 EF 代码会有所帮助,以便我们为您提供更具体的答案。
注意:在 ReOrder 进程将所有记录加载到内存时存在大量数据集的情况下,不要在 EF 中使用此逻辑,但它对于管理 序数性很有用 em> 在由附加过滤子句限定的子列表或子列表中(因此不适用于整个表!)
如果您需要在整个表中执行唯一的排名逻辑,那么独立的 ReOrder 过程本身就是一个很好的候选者,可以作为存储过程进入数据库
这里有两个主要变体(用于唯一值):
- 排名必须始终是连续的/连续的
- 这简化了插入和替换逻辑,但您可能必须在代码中管理添加、插入、交换和删除方案。
- 在排名中上下移动项目的代码非常容易实现
- 必须管理删除以重新计算所有项目的排名
- 排名可能有间隙(并非所有值都是连续的)
- 这听起来应该更容易,但要评估在列表中上下移动意味着您必须考虑到差距。
我不会发布此变体的代码,但请注意维护起来通常更复杂。
- 另一方面,您无需担心主动管理删除。
当需要管理序数时,我使用以下例程。
注意:此例程不保存更改,它只是将所有可能受影响的记录加载到内存中,以便我们可以正确处理新排名。
public static void ReOrderTableRecords(Context db)
{
// By convention do not allow the DB to do the ordering. this type of query will load missing DB values into the current dbContext,
// but will not replace the objects that are already loaded.
// The following query would be ordered by the original DB values:
// db.Table.OrderBy(x => x.Order).ToList()
// Instead we want to order by the current modified values in the db Context. This is a very important distinction which is why I have left this comment in place.
// So, load from the DB into memory and then order:
// db.Table[.Where(...optional filter by parentId...)].ToList().OrderBy(x => x.Order)
// NOTE: in this implementation we must also ensure that we don't include the items that have been flagged for deletion.
var currentValues = db.Table.ToList()
.Where(x => db.Entry(x).State != EntityState.Deleted)
.OrderBy(x => x.Rank);
int order = 1;
foreach (var item in currentValues)
item.Order = order++;
}
假设您可以将代码简化为将具有特定排名的新项目插入列表中的函数,或者您想要交换列表中两个项目的排名:
public static Table InsertItem(Context db, Table item, int? Rank = 1)
{
// Rank is optional, allows override of the item.Rank
if (Rank.HasValue)
item.Rank = Rank;
// Default to first item in the list as 1
if (item.Rank <= 0)
item.Rank = 1;
// re-order first, this will ensure no gaps.
// NOTE: the new item is not yet added to the collection yet
ReOrderTableRecords(db);
var items = db.Table.ToList()
.Where(x => db.Entry(x).State != EntityState.Deleted)
.Where(x => x.Rank >= item.Rank);
if (items.Any())
{
foreach (var i in items)
i.Rank = i.Rank + 1;
}
else if (item.Rank > 1)
{
// special case
// either ReOrderTableRecords(db) again... after adding the item to the table
item.Rank = db.Table.ToList()
.Where(x => db.Entry(x).State != EntityState.Deleted)
.Max(x => x.Rank) + 1;
}
db.Table.Add(item);
db.SaveChanges();
return item;
}
/// <summary> call this when Rank value is changed on a single row </summary>
public static void UpdateRank(Context db, Table item)
{
var rank = item.Rank;
item.Rank = -1; // move this item out of the list so it doesn't affect the ranking on reOrder
ReOrderTableRecords(db); // ensure no gaps
// use insert logic
var items = db.Table.ToList()
.Where(x => db.Entry(x).State != EntityState.Deleted)
.Where(x => x.Rank >= rank);
if (items.Any())
{
foreach (var i in items)
i.Rank = i.Rank + 1;
}
item.Rank = rank;
db.SaveChanges();
}
public static void SwapItemsByIds(Context db, int item1Id, int item2Id)
{
var item1 = db.Table.Single(x => x.Id == item1Id);
var item2 = db.Table.Single(x => x.Id == item2Id);
var rank = item1.Rank;
item1.Rank = item2.Rank;
item2.Rank = rank;
db.SaveChanges();
}
public static void MoveUpById(Context db, int item1Id)
{
var item1 = db.Table.Single(x => x.Id == item1Id);
var rank = item1.Rank - 1;
if (rank > 0) // Rank 1 is the highest
{
var item2 = db.Table.Single(x => x.Rank == rank);
item2.Rank = item1.Rank;
item1.Rank = rank;
db.SaveChanges();
}
}
public static void MoveDownById(Context db, int item1Id)
{
var item1 = db.Table.Single(x => x.Id == item1Id);
var rank = item1.Rank + 1;
var item2 = db.Table.SingleOrDefault(x => x.Rank == rank);
if (item2 != null) // item 1 is already the lowest rank
{
item2.Rank = item1.Rank;
item1.Rank = rank;
db.SaveChanges();
}
}
为确保不引入间隙,您应该在从表中删除项目之后调用ReOrder,但在调用SaveChanges()之前调用
或者在每个 Swap/MoveUp/MoveDown 之前调用ReOrder,类似于插入。
请记住,允许重复的 Rank 值要简单得多,尤其是对于大型数据列表,但您的业务需求将决定这是否是一个可行的解决方案。