我的问题涉及一个任务表及其关系。
业务模型是每个任务都有一个任务列表,需要在它开始之前完成。
我的C#EF6 winforms应用程序有以下2个表(简化)
我有一个带主键的任务表
[TaskID] [int] IDENTITY(1,1) NOT NULL
和只有两列的前趋表
[TaskID] [int] NOT NULL,
[PredecessorTaskId] [int] NOT NULL
其中主键由两列组成。
TaskID
和PredecessorTaskId
都是任务表的外键。
在设计器edmx中,我看不到前一个表,而是从Task表中运行一条回到自身的行。
通过运行自定义工具创建的代码是
public partial class task
{
public task()
{
this.NeedsTasks = new HashSet<task>();
this.NeededByTasks = new HashSet<task>();
}
public int TaskID { get; set; }
public virtual ICollection<task> NeedsTasks { get; set; }
public virtual ICollection<task> NeededByTasks { get; set; }
// other fields
}
我找不到为前任表格生成的任何代码
现在我的代码
public static void SaveWorkflow(List<task> workflowTasks)
{
short offset = 1;
using (var db = new MyDbContext)
{
task task = null;
task prevtask = null;
foreach (var workflowTask in workflowTasks)
{
workflowTask.TaskOffsetID = offset;
offset++;
task = db.tasks.Add(workflowTask);
//db.SaveChanges();
if (prevtask != null)
{
task.NeedsTasks.Add(prevtask);
}
prevtask = task;
}
db.SaveChanges();
}
}
如果我运行上面的代码并使用
查询记录select k.taskid, p.taskid, p.predecessortaskId
from task k left outer join predecessor p on k.taskid = p.taskid
where etc
我得到了
taskid taskid predecessortaskid
568187 568187 568188
568188 NULL NULL
568189 568189 568187
568190 568190 568189
568191 568191 568190
这不是我想要的,因为它是应该拥有空数据的第一个前任,表明第一个任务不需要任何其他任务来启动。
如果我取消注释循环内对SaveChanges的调用,我会得到正确的结果
但是我更希望只有一次调用SaveChanges,所以有一个交易。
我也尝试添加
prevtask.NeededByTasks.Add(task);
之后
task.NeedsTasks.Add(prevtask);
然而,它没有任何区别
答案 0 :(得分:1)
实际上是您的SQL结果中包含workflowTasks
数据的第一个前任(即NULL
列表中的第一个元素) 。如果稍微扩展SQL查询,可以看到它:
select k.taskid, p.taskid, p.predecessortaskId, k.taskoffsetid
from task k left outer join predecessor p on k.taskid = p.taskid
where etc
order by taskoffsetid
结果现在是:
taskid taskid predecessortaskid taskoffsetid
568188 NULL NULL 1
568187 568187 568188 2
568189 568189 568187 3
568190 568190 568189 4
568191 568191 568190 5
但是,taskid
的生成顺序与您设置taskoffsetid
的顺序不同,并且任务在输入列表中排序。
如果插入包含许多实体的复杂对象图,则无法假定生成密钥的任何特定顺序。 EF发送到数据库的INSERT语句序列受EF控制。我不会以任何方式依赖于期望密钥是按照我想要的特定顺序生成的,除非我通过多次调用SaveChanges
显式强制密钥生成(正如您已经看到的那样)。
通过将整个循环包装到SaveChanges
块中,您可以解决在单个事务中运行多个using (var scope = new TransactionScope()) { ... scope.Complete(); }
调用的问题。
然而,问题是:你真的需要排序taskid
吗?根本没有引用密钥,您实际上可以按照依赖项的顺序运行任务,例如:
using (var db = new MyDbContext)
{
var task = db.Tasks.Where(t => !t.NeedsTasks.Any()).SingleOrDefault();
while (task != null)
{
// run task / do something with task...
task = task.NeededByTasks.SingleOrDefault(); // lazy loading
}
}