私はこの問題に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 ステートメントを生成し、それらを必要な順序で実行する方が簡単だと思い始めています。
これはここでの最初の質問なので、長さと書式設定についてお詫び申し上げます...読みやすいことを願っています:-)