1

私はこの問題に1週間取り組んできましたが、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 内で、新しいタスクをワークフローに追加したり、既存のタスクを編集したり、タスクを削除したりできます。タスクのレベルが 1 つしかない場合 (定義 => タスク)、これは簡単なことです。私の問題は、無限レベル (定義 => タスク => 子タスク => 子タスクなど) の可能性があることにあります。

現在、データベースから既存のワークフローを取得し、値を割り当てます (ワークフローは渡される値であり、タイプは 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);

次に、タスクのリストをループして、それらを定義に追加するか、それらを見つけて値を設定します。コンテキスト内のワークフローに WorkflowDefinition を設定する SetDefinition という名前の WorkflowTask オブジェクトに関数を追加しました (以前は、ID が一致していても親ワークフローが別のものであると考えていたため、キー エラーが発生していました)。コンテナーの場合は、再帰関数を実行して、すべての子をコンテキストに追加しようとします。

<!-- 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は、2 段階のプロセスを実行する必要がありました。ステップ 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 は、保存を押すまですべての変更をメモリに保持するため、最初に子を削除しても、制約エラーがスローされます。おそらくカスケード削除などを使用して、子を別の方法でマッピングする必要があると考えています。また、削除ループでタスクをループすると、コンテナにフラグが付けられ、その結果として子が削除されると予想される場合にのみ、コンテナと子の両方に削除のフラグが付けられます。

最後に、上記の保存部分を理解するのにかなりの 1 週間かかりましたが、非常に複雑に見えます。これを行う簡単な方法はありますか?私は EF にかなり慣れていないので、コードで SQL ステートメントを生成し、それらを必要な順序で実行する方が簡単だと思い始めています。

これはここでの最初の質問なので、長さと書式設定についてお詫び申し上げます...読みやすいことを願っています:-)

4

2 に答える 2

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 コードで気付くことの 1 つは、ID と Order でタスクを検索していることです。コンテナーに 2 つの新しい子を追加すると、両方の ID がゼロになり、最初の ID が見つかったために 2 番目の ID が追加されないため、これを行う必要がありました。各タスクには固有の順序があるため、それを使用してさらに区別しました。

私はこのコードについてあまり良いとは思いませんが、私はこの問題に長い間取り組んできましたが、これは機能するので、今のところはそのままにしておきます。すべての情報を網羅したことを願っています。実際に何人の人がこれを必要とするかはわかりませんが、決してわかりません.

于 2013-09-26T13:58:49.703 に答える
0

1 つの提案です。私はこの無限の再帰の可能性がある状況では使用していませんが、削除時にカスケードをネイティブに動作させたい場合、すべてのタスクが親タスクによって直接所有されているように見える場合は、定義することでアプローチできます。識別関係:

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

この質問は関連しています: EF は、親が削除されていない孤立したデータを自動的に削除できますか?

編集: そしてこのリンク: https://stackoverflow.com/a/4925040/1803682

于 2013-09-09T16:16:27.503 に答える