【发布时间】:2017-03-10 03:52:18
【问题描述】:
更新:这里是完整代码https://dotnetfiddle.net/eAeWp5
这比我想象的要困难得多。
在实际项目中,我需要更新一个数据库表,其中包含一个列Position(用于排序顺序),但所有方法获取的是一个列表,其中仅包含具有新位置的已更改对象。表和类是WatchList。
这里是:
public class WatchList : IEquatable<WatchList>
{
public WatchList(int id)
{
Id = id;
}
public int Id { get; }
public string Name { get; set; }
public int UserId { get; set; }
public int Position { get; set; }
public bool Equals(WatchList other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
return this.Id == other.Id;
}
public override bool Equals(object obj)
{
WatchList other = obj as WatchList;
return this.Equals(other);
}
public override int GetHashCode()
{
return this.Id;
}
public override string ToString()
{
return $"WatchListId:{Id} Name:{Name} UserId:{UserId} Position:{Position}";
}
}
所以WatchListId 是主键,Position 是我要更新的列。
考虑该表包含以下监视列表:
WatchListId Position
1 1
2 2
3 3
4 4
5 5
用户想修改订单,拖拽,最后提交给服务器。客户端将调用 UpdateWatchListsSort 并使用仅包含用户移动的 WatchLists 的列表。
考虑用户移动
1 ---> 5
3 ---> 1
5 ---> 4
所以数据库中的新(正确)顺序是:
WatchListId Position
3 1
2 2
4 3
5 4
1 5
您注意到,即使是其他一些观察列表也必须更新,因为如果它们的位置受到影响,位置需要向上移动 1。这是它变得棘手。所有未移动到位置的项目应保持稳定的顺序(Position)。在这种情况下,ID=2 和 ID=4 应该保持这个顺序。
样本:
class Program
{
static void Main(string[] args)
{
var changedWatchLists = new List<WatchList>
{
new WatchList(1) {Position = 5}, new WatchList(3) {Position = 1}, new WatchList(5) {Position = 4}
};
WatchList.UpdateWatchListsSort("123", changedWatchLists);
}
}
我的方法是首先加载完整的List<WatchList>(来自数据库),然后将其与具有新职位的传递列表合并。这样可以在之前验证输入,并且应该使其更简单,因为所有操作都可以在内存中完成。
基本逻辑是将Remove从完整列表中全部更改为WatchLists,然后将Insert放在所需位置。
我只列举了按新职位排序的更改列表以避免副作用。否则List.Insert 可以向上移动已经有目标位置的项目。
但是,最后我仍然有物品在错误的位置,所以我被卡住了。
完整方法UpdateWatchListsSort:
public static void UpdateWatchListsSort(string userId, List<WatchList> watchListsWithModifiedPosition)
{
List<WatchList> allUserWatchLists = GetWatchListsFromDb(userId);
// mapping WatchListId --> WatchList (from DB)
Dictionary<int, WatchList> dbWatchListIdLookup = allUserWatchLists.ToDictionary(w => w.Id);
if (watchListsWithModifiedPosition.Count == allUserWatchLists.Count)
allUserWatchLists = watchListsWithModifiedPosition;
else
{
// enumerate all modified WatchLists ordered by position ascending (to avoid side affects)
foreach (WatchList modified in watchListsWithModifiedPosition.OrderBy(w => w.Position))
{
WatchList dbWatchList = dbWatchListIdLookup[modified.Id];
int newIndex = modified.Position - 1;
int oldIndex = allUserWatchLists.IndexOf(dbWatchList); // might be at a different position meanwhile( != db-position )
allUserWatchLists.RemoveAt(oldIndex);
// if moved forwards index is index-1 because the watchlist was already removed at List.RemoveAt,
// if moved backwards index isn't affected
bool movedForwards = newIndex > oldIndex;
if (movedForwards)
newIndex--;
allUserWatchLists.Insert(newIndex, dbWatchList);
}
}
var changeInfos = allUserWatchLists
.Select((wl, index) => new { WatchList = wl, NewPosition = index + 1 })
.Where(x => x.WatchList.Position != x.NewPosition)
.ToList();
foreach (var change in changeInfos)
{
WatchList wl = change.WatchList;
wl.Position = change.NewPosition;
// check if the new position is equal to the position given as parameter
Debug.Assert(wl.Position == watchListsWithModifiedPosition
.Where(w => w.Id == wl.Id)
.Select(w => w.Position)
.DefaultIfEmpty(wl.Position)
.First());
}
// check if allUserWatchLists contains duplicate Positions which is invalid
Debug.Assert(allUserWatchLists
.Select(w => w.Position)
.Distinct().Count() == allUserWatchLists.Count);
// update changeInfos.Select(x => x.WatchList) via table-valued-parameter in DB (not related) .....
}
private static List<WatchList> GetWatchListsFromDb(string userId)
{
var allDbWatchLists = new List<WatchList>
{
new WatchList(1) {Position = 1}, new WatchList(2) {Position = 2}, new WatchList(3) {Position = 3},
new WatchList(4) {Position = 4}, new WatchList(5) {Position = 5}
};
return allDbWatchLists;
}
如果您执行此示例,此 Debug.Assert 将失败:
// check if the new position is equal to the position given as parameter
Debug.Assert(wl.Position == watchListsWithModifiedPosition
.Where(w => w.Id == wl.Id)
.Select(w => w.Position)
.DefaultIfEmpty(wl.Position)
.First());
所以算法是错误的,因为 WatchList 新的 Position 不是所需的(作为参数给出)。
我希望你理解这个要求,看看我做错了什么。我怀疑这部分但不知道如何修复它:
// if moved forwards index is index-1 because the watchlist was already removed at List.RemoveAt,
// if moved backwards index isn't affected
bool movedForwards = newIndex > oldIndex;
if (movedForwards)
newIndex--;
也许你有更好的方法,可读性很重要。
【问题讨论】:
-
为了清楚一点:你得到用户实际执行的操作列表,按照他执行的顺序?例如,如果表中有 ID [1000,2000],并且得到 [2000->1, 1000->1],那么表应该保持不变吗?
-
@MattTimmermans:我得到了
WatchList的列表,其中仅包含位置已更改但不包含受影响的位置(Fe WatchList 从 2->1 更改,因此 WatchList 1 必须向上移动到 2,在这种情况下,我只得到WatchList ID=2 Position=1)。这是为了减少网络流量。否则,如果将最后一个移到第一个位置,则必须传递所有 WatchList(可能是 10000 个)。