我有以下结构:
public interface IChainByPreviousId
{
int Id { get; set; }
int? PreviousDeviceId { get; set; }
}
public class RegisteredDevice : IChainByPreviousId
{
public int Id { get; set; }
public int? PreviousDeviceId { get; set; }
public int? Position { get; set; }
public string DeviceName { get; set; }
public string ModelNumber { get; set; }
public virtual RegisteredDevice? PreviousDevice { get; set; }
}
因此,正如我们所看到的,每个设备都可以有以前的设备或为空(如果它是我们订购列表中的第一个设备)
builder.HasOne(a => a.PreviousDevice)
.WithOne()
.HasForeignKey<RegisteredDevice>(p => p.PreviousDeviceId)
.OnDelete(DeleteBehavior.NoAction);
然后我想“重新排序”设备。
model.DeviceReordered
是的集合
public class DeviceReorderedDto
{
public int DeviceId { get; set; }
public int? NewPreviousDeviceId { get; set; }
}
所以,这个类描述了重新排序设备(新的先前的deviceId)
对于我的情况,有(将 Id == 2 的设备从第二个位置(id=1 之后)移动到 id == 4 和 id == 5 之间的设备):
[
{
"DeviceId": 3,
"NewPreviousDeviceId": 1
},
{
"DeviceId": 2,
"NewPreviousDeviceId": 4
},
{
"DeviceId": 5,
"NewPreviousDeviceId": 2
}
]
获取设备:
var localDevices = _dataContext.RegisteredDevices.ToList();
init
localDevices
(获取数据之后和应用更改之前)如下:
[
{
"Id": 1,
"ModelNumber": "20I",
"CompanyName": "J. J. Keller \u0026 Associates, Inc.",
"DeviceName": "J. J. Keller ELD - iOS 2.0",
"PreviousDeviceId": null,
"Position": 0
},
{
"Id": 2,
"ModelNumber": "25I",
"CompanyName": "J. J. Keller \u0026 Associates, Inc.",
"DeviceName": "J. J. Keller ELD - iOS 2.5",
"PreviousDeviceId": 1,
"Position": 1
},
{
"Id": 3,
"ModelNumber": "FLT3",
"CompanyName": "HOS247 LLC",
"DeviceName": "#1 ELD by HOS247",
"PreviousDeviceId": 2,
"Position": 2
},
{
"Id": 4,
"ModelNumber": "N775G",
"CompanyName": "XPERT-IT SOLUTIONS INC.",
"DeviceName": "Xpert ELD",
"PreviousDeviceId": 3,
"Position": 3
},
{
"Id": 5,
"ModelNumber": "PMG001",
"CompanyName": "PeopleNet",
"DeviceName": "PeopleNet Mobile Gateway - Trimble Driver ELD",
"PreviousDeviceId": 4,
"Position": 4
}
]
并应用更改:
for (int i = 0; i < model.DeviceReordered.Count; i++)
{
var item = model.DeviceReordered[i];
var device = localDevices.Where(a => a.Id == item.DeviceId).First();
var previousDevice = localDevices.Where(a => a.Id == item.NewPreviousDeviceId).FirstOrDefault();
device.PreviousDevice = previousDevice;
}
localDevices = localDevices.OrderBy(a => a.Position).ToList();
并保存更改:
await _dataContext.SaveChangesAsync();
我遇到错误:
无法在对象“dbo.RegisteredDevice”中插入重复的键行 唯一索引“IX_RegisteredDevice_PreviousDeviceId”。重复的键 值为 (4)。
但是我在
localDevices
中没有重复项
好的,在致电 await _dataContext.SaveChangesAsync();
之前我添加:ValidateDuplicates(list);
void ValidateDuplicates(IList<T> positionList)
{
var duplicates = positionList
.Where(x => x.PreviousDeviceId.HasValue)
.GroupBy(x => x.PreviousDeviceId)
.Where(g => g.Count() > 1)
.Select(x => x.Key).ToList();
if (duplicates.Any())
throw new PositionReorderDuplicateException(duplicates);
}
无重复。好的,再检查一下:
var prev = localDevices.Where(a => a.PreviousDeviceId == 4).ToList();
prev
只有一条记录。
我的
localDevices
打电话前await _dataContext.SaveChangesAsync()
:
[
{
"Id": 1,
"ModelNumber": "20I",
"CompanyName": "J. J. Keller \u0026 Associates, Inc.",
"DeviceName": "J. J. Keller ELD - iOS 2.0",
"PreviousDeviceId": null,
"Position": 0
},
{
"Id": 3,
"ModelNumber": "FLT3",
"CompanyName": "HOS247 LLC",
"DeviceName": "#1 ELD by HOS247",
"PreviousDeviceId": 1,
"Position": 1
},
{
"Id": 4,
"ModelNumber": "N775G",
"CompanyName": "XPERT-IT SOLUTIONS INC.",
"DeviceName": "Xpert ELD",
"PreviousDeviceId": 3,
"Position": 2
},
{
"Id": 2,
"ModelNumber": "25I",
"CompanyName": "J. J. Keller \u0026 Associates, Inc.",
"DeviceName": "J. J. Keller ELD - iOS 2.5",
"PreviousDeviceId": 4,
"Position": 3
},
{
"Id": 5,
"ModelNumber": "PMG001",
"CompanyName": "PeopleNet",
"DeviceName": "PeopleNet Mobile Gateway - Trimble Driver ELD",
"PreviousDeviceId": 2,
"Position": 4
}
]
所以,看来一切都好。按预期重新排序,没有重复,只有一台设备有
PreviousDeviceId == 4
我的完整代码是:
var localDevices = _dataContext.RegisteredDevices.ToList();
if (model.DeviceReordered.Any())
{
for (int i = 0; i < model.DeviceReordered.Count; i++)
{
var item = model.DeviceReordered[i];
var device = localDevices.Where(a => a.Id == item.DeviceId).First();
var previousDevice = localDevices.Where(a => a.Id == item.NewPreviousDeviceId).FirstOrDefault();
device.PreviousDeviceId = previousDevice?.Id;
}
}
int? previousId = null;
var liveRecordsCount = localDevices.Where(a => a.Position.HasValue).Count();
for (int i = 0; i < liveRecordsCount; i++)
{
var device = localDevices.Where(a => a.PreviousDeviceId == previousId).First();
device.Position = i;
previousId = device.Id;
}
localDevices = localDevices.OrderBy(a => a.Position).ToList();
await _dataContext.SaveChangesAsync();
已编译的项目,其中包含我添加到的问题https://github.com/oshastitko/reordering_problem
我的代码有什么问题?
我建议将修改分成几个步骤(最好是在一个事务中,这样就不会发生不一致的状态) - 1)“清理”所有更改项目的先前设备 ID 2)更新先前的设备 ID 3)重新计算位置:
// clean the previous ids
_dataContext.RegisteredDevices
.Where(device => model.Select(dto => dto.DeviceId).Contains(device.Id))
.ExecuteUpdate(up => up.SetProperty(device => device.PreviousDeviceId, (int?)null));
// set new ones
var changedDevices = _dataContext.RegisteredDevices
.Where(device => model.Select(dto => dto.DeviceId).Contains(device.Id))
.ToList();
for (int i = 0; i < model.Count; i++)
{
var item = model[i];
var device = changedDevices.Where(a => a.Id == item.DeviceId).First();
device.PreviousDeviceId = item.NewPreviousDeviceId;
}
_dataContext.SaveChanges();
// recalculate and update the positions
_dataContext.ChangeTracker.Clear();
var localDevices = _dataContext.RegisteredDevices.ToList();
// ...
启用 查询日志记录,据我记得 EF 会按 1 发送更新,这样当您尝试插入仍被某人使用的
PreviousDeviceId
时,您可能会遇到这样的情况。
提交了 PR,其中包含对我有用的代码。