EF - 使用子级更新记录

时间:2013-09-09 15:54:35

标签: c# entity-framework save subclass superclass

我已经在这个问题上工作了一个星期,而且我对EF感到非常沮丧。首先我有一张超级桌子 - >子表模式在数据库中进行。它采用代码优先的方法设计。超类型称为WorkflowTask,定义如下:

<!-- language: c# -->
public abstract class WorkflowTask 
{
    public int WorkflowTaskId { get; set; }
    public int Order { get; set; }        
    public WorkflowTaskType WorkflowTaskType { get; set; }
    public WorkFlowTaskState State { get; set; }
    public ParentTask ParentTask { get; set; }        
    public WorkflowDefinition WorkflowDefinition { get; set; }
}

示例子任务将从此任务继承并提供其他属性:

<!-- language: c# -->
public class DelayTask : WorkflowTask
{
    public int Duration { get; set; }
}

这将映射到数据库,如下所示:

<!-- language: c# -->
public class WorkflowTaskEntityConfiguration : EntityTypeConfiguration<WorkflowTask>
{
    public WorkflowTaskEntityConfiguration()
    {

        HasKey(w => w.WorkflowTaskId);
        Property(w => w.WorkflowTaskId).HasColumnName("Id");
        Property(w => w.Order).HasColumnName("Order");
        Property(w => w.WorkflowTaskType).HasColumnName("TaskTypeId");
        Property(w => w.State).HasColumnName("TaskStateId");
        HasOptional(c => c.ParentTask).WithMany()
                .Map(c => c.MapKey("ParentTaskId"));
    }
}

延迟任务映射如下:

<!-- language: c# -->
public class DelayTaskEntityConfiguration : EntityTypeConfiguration<DelayTask>
{
    public DelayTaskEntityConfiguration()
    {
        Property(d => d.WorkflowTaskId).HasColumnName("DelayTaskId");
        Property(d => d.Duration).HasColumnName("Duration");    
    }
} 

希望你明白了。现在我有另一个称为容器任务的子类型。此任务将执行其他任务,并可能保留其他容器任务。以下是它的外观和映射:

<!-- language: c# -->
public class ContainerTask : ParentTask
{
    public ContainerTask()
    {
        base.WorkflowTaskType = WorkflowTaskType.Container;
        base.ParentTaskType = ParentTaskType.ContainerTask;
    }        
    public List<WorkflowTask> ChildTasks { get; set; }
}

public class ContainerTaskEntityConfiguration : EntityTypeConfiguration<ContainerTask>
{
    public ContainerTaskEntityConfiguration()
    {            

        Property(x => x.WorkflowTaskId).HasColumnName("ContainerTaskId");           
        HasMany(c => c.ChildTasks).WithMany()
            .Map(c => c.ToTable("ContainerTaskChildren", WorkflowContext.SCHEMA_NAME)
                       .MapLeftKey("ContainerTaskId")
                       .MapRightKey("ChildTaskId"));                  
    }
}

确保我包含所有内容;这是ParentTask对象及其映射:

<!-- language: c# -->
public abstract class ParentTask : WorkflowTask
{
    public ParentTaskType ParentTaskType {get; set;}
}

public class ParentTaskEntityConfiguration : EntityTypeConfiguration<ParentTask>
{
    public ParentTaskEntityConfiguration()
    {
        Property(w => w.WorkflowTaskId).HasColumnName("ParentTaskId");
        Property(w => w.ParentTaskType).HasColumnName("ParentTaskTypeId");
    }
}

现在我要保存的项目是WorkflowDefinition对象。它将按顺序执行一堆任务。它的定义如下:

<!-- language: c# -->
public class WorkflowDefinition 
{
    public int WorkflowDefinitionId { get; set; }
    public string WorkflowName { get; set; }
    public bool Enabled { get; set; }

    public List<WorkflowTask> WorkflowTasks { get; set; }
}

public class WorkflowDefinitionEntityConfiguration :
                                   EntityTypeConfiguration<WorkflowDefinition>
{
    public WorkflowDefinitionEntityConfiguration()
    {
        Property(w => w.WorkflowDefinitionId).HasColumnName("Id");
        HasMany(w => w.WorkflowTasks)
            .WithRequired(t=> t.WorkflowDefinition)               
            .Map(c => c.MapKey("WorkflowDefinitionId"));

        Property(w => w.Enabled).HasColumnName("Enabled");
        Property(w => w.WorkflowName).HasColumnName("WorkflowName");
    }
}

所以定义了所有我将WorkflowDefinition对象传递到我的数据存储库层并希望使用EF保存它。因为在UI中处理对象时,对象已丢失了它的上下文;我必须重新关联它,以便它知道要保存什么。在UI中,我可以向工作流添加新任务,编辑现有任务以及删除任务。如果只有一个级别的任务(定义=&gt;任务),这将是蛋糕。我的问题在于存在无限级别的可能性(definition =&gt; tasks =&gt; childtasks =&gt; childtasks等...)。

目前,我从数据库中检索现有工作流并分配值(工作流是传入的值,属于WorkflowDefinition类型):

<!-- language: c# -->
// retrieve the workflow definition from the database so that it's within our context
var dbWorkflow = context.WorkflowDefinitions
                    .Where(w => w.WorkflowDefinitionId ==workflow.WorkflowDefinitionId)
                    .Include(c => c.WorkflowTasks).Single();

// transfer the values of the definition to the one we retrieved.
context.Entry(dbWorkflow).CurrentValues.SetValues(workflow);

然后我遍历任务列表并将它们添加到定义中或找到它们并设置它们的值。我在WorkflowTask对象中添加了一个名为SetDefinition的函数,它将WorkflowDefinition设置为上下文中的工作流(以前我会得到一个关键错误,因为它认为父工作流是不同的,即使Ids匹配)。如果它是一个容器,我运行一个递归函数来尝试将所有子项添加到上下文中。

<!-- language: c# -->
foreach (var task in workflow.WorkflowTasks)
{
    task.SetDefinition(dbWorkflow);

    if (task.WorkflowTaskId == 0)
    {
        dbWorkflow.WorkflowTasks.Add(task);
    }
    else
    {
        WorkflowTask original = null;
        if (task is ContainerTask)
        {
            original = context.ContainerTasks.Include("ChildTasks")
                                .Where(w => w.WorkflowTaskId == task.WorkflowTaskId)
                                .FirstOrDefault();
            var container = task as ContainerTask;
            var originalContainer = original as ContainerTask;
            AddChildTasks(container, dbWorkflow, context, originalContainer);
        }
        else
        {
            original = dbWorkflow.WorkflowTasks.Find(t => t.WorkflowTaskId == 
                                                             task.WorkflowTaskId);
        }
        context.Entry(original).CurrentValues.SetValues(task);
    }
}

AddChildTasks函数如下所示:

<!-- language: c# -->
private void AddChildTasks(ContainerTask container, WorkflowDefinition workflow, 
                           WorkflowContext context, ContainerTask original)
    {
        if (container.ChildTasks == null) return;

        foreach (var task in container.ChildTasks)
        {
            if (task is ContainerTask)
            {
                var subContainer = task as ContainerTask;
                AddChildTasks(subContainer, workflow, context, container);
            }

            if (task.WorkflowTaskId == 0)
            {
                if (container.ChildTasks == null) 
                    container.ChildTasks = new List<WorkflowTask>();
                original.ChildTasks.Add(task);
            }
            else
            {
                var originalChild = original.ChildTasks
                       .Find(t => t.WorkflowTaskId == task.WorkflowTaskId);
                context.Entry(originalChild).CurrentValues.SetValues(task);
            }
        }
    }

要删除任务,ve found I必须执行两个步骤。步骤1涉及完成原始定义并标记不再在传递的定义中删除的任务。第2步只是将这些任务的状态设置为已删除。

<!-- language: c# -->
var deletedTasks = new List<WorkflowTask>();
foreach (var task in dbWorkflow.WorkflowTasks)
{
    if (workflow.WorkflowTasks.Where(t => t.WorkflowTaskId == 
                  task.WorkflowTaskId).FirstOrDefault() == null)
        deletedTasks.Add(task);
}

foreach (var task in deletedTasks)
    context.Entry(task).State = EntityState.Deleted;

这是我遇到问题的地方。如果我删除容器,我会收到一个约束错误,因为容器包含子容器。 UI保存内存中的所有更改,直到我点击保存,所以即使我先删除了孩子,它仍然会抛出约束错误。我认为我需要以不同的方式映射孩子,可能是级联删除或其他东西。此外,当我遍历delete循环中的任务时,当我只希望标记容器并删除子项时,容器和子标记都会被标记为删除。

最后,上面的保存部分花了我一个很好的一周来弄清楚它看起来很复杂。有更简单的方法吗?我是EF的新手,我开始认为让代码生成SQL语句并按照我想要的顺序运行它会更容易。

这是我在这里的第一个问题,所以我为长度和格式道歉...希望它清晰可辨: - )

2 个答案:

答案 0 :(得分:0)

一个建议,我没有在有这种无休止的可能递归的情况下使用它,但是如果你希望删除级联在本地工作并且看起来所有任务都属于某个父任务直接拥有你可以接近它通过定义识别关系:

modelBuilder.Entity<WorkFlowTask>().HasKey(c => new {c.WorkflowTaskID, 
                    c.ParentTask.WofkflowTaskId});

这个问题很相关:Can EF automatically delete data that is orphaned, where the parent is not deleted?

修改:此链接:https://stackoverflow.com/a/4925040/1803682

答案 1 :(得分:0)

好吧,它花了我比我希望的更长的时间,但我想我终于明白了。至少我的所有测试都在通过,到目前为止我所保存的所有定义都已经完成了。可能有一些我没想过的组合,但现在我至少可以转向别的东西了。

首先,我换了一棵树作为树,然后决定把它弄平。这只是意味着所有任务都可以从根目录中看到,但我仍然需要设置父属性以及子属性。

foreach (var task in workflow.WorkflowTasks)
    {
    taskIds.Add(task.WorkflowTaskId);  //adding the ids of all tasks to use later
    task.SetDefinition(dbWorkflow);    //sets the definition to record in context
    SetParent(context, task);          //Attempt to set the parent for any task

    if (task.WorkflowTaskId == 0)
    {
        // I found if I added a task as a child it would duplicate if I added it
        // here as well so I only add tasks with no parents
        if (task.ParentTask == null)
            dbWorkflow.WorkflowTasks.Add(task);
    }
    else
    {
        var dbTask = dbWorkflow.WorkflowTasks.Find(t => t.WorkflowTaskId == task.WorkflowTaskId);
        context.Entry(dbTask).CurrentValues.SetValues(task);
    }
}

SetParent函数必须检查任务是否具有父项,并确保父项不是新任务(id == 0)。然后它尝试在定义的上下文版本中找到父级,这样我就不会得到重复项(即 - 如果父级未被引用,它会尝试添加一个新的,即使它存在于数据库中)。一旦确定了父母,我会检查它的孩子,看看那个任务是否已经存在,如果没有我添加它。

private void SetParent(WorkflowContext context, WorkflowTask task)
    {
        if (task.ParentTask != null && task.ParentTask.WorkflowTaskId != 0)
        {
            var parentTask = context.WorkflowTasks.Where(t => t.WorkflowTaskId == task.ParentTask.WorkflowTaskId).FirstOrDefault();
            var parent = parentTask as ParentTask;
            task.ParentTask = parent;
            if (parentTask is ContainerTask)
            {
                var container = context.ContainerTasks.Where(c => c.WorkflowTaskId == parentTask.WorkflowTaskId).Include(c => c.ChildTasks).FirstOrDefault() as ContainerTask;
                if (container.ChildTasks == null)
                    container.ChildTasks = new List<WorkflowTask>();
                var childTask = container.ChildTasks.Find(t => t.WorkflowTaskId == task.WorkflowTaskId
                                                                && t.Order == task.Order);

                if(childTask == null)
                    container.ChildTasks.Add(task);
            }
        }
    }

您将在SetParent代码中注意到的一件事是我正在通过ID和订单搜索任务。我必须这样做,因为如果我将两个新的子项添加到容器中,两个Ids将为零,第二个不会被添加,因为它找到了第一个。每个任务都有一个独特的顺序,所以我用它来进一步区分它们。

我觉得这段代码并不是很棒,但是我一直在研究这个问题已经有很长时间了,所以我现在就把这个问题留下来了。我希望我能够涵盖所有的信息,我不太确定有多少人会真正需要这些,但你永远不会知道。