0

I am trying to map a class where i have a list of related items and a selected related item. Basically, I have a workflow with a collection of tasks, and at any given time one of those tasks is the selected as the current task.

public class Flow
{
    public int FlowId { get; set; }
    public int CurrentFlowTaskId { get; set; }
    public bool IsActive { get; set; }

    public virtual FlowTask CurrentFlowTask { get; set; }
    public virtual ICollection<FlowTask> FlowTasks { get; set; }
}

public class FlowTask
{
    public int FlowTaskId { get; set; }
    public int FlowId { get; set; }
    public string Discription { get; set; }
    public virtual Flow Flow { get; set; }
}

And my mapping looks like this:

public class FlowMap : EntityTypeConfiguration<Flow>
{
    public FlowMap()
    {
        HasKey(x => x.FlowId);
        Property(x => x.IsActive).IsRequired();

        HasOptional(x => x.CurrentFlowTask)
            .WithOptionalPrincipal()
            .WillCascadeOnDelete(false);

        HasMany(x => x.FlowTasks)
            .WithRequired(x => x.Flow)
            .HasForeignKey(x => x.FlowId)
            .WillCascadeOnDelete(false);
    }
}

public class FlowTaskMap : EntityTypeConfiguration<FlowTask>
{
    public FlowTaskMap()
    {
        HasKey(x => x.FlowTaskId);
        Property(x => x.Discription).HasMaxLength(25).IsRequired();
    }
}

This creates a migration that looks like this:

CreateTable(
    "dbo.Flows",
    c => new
        {
            FlowId = c.Int(nullable: false, identity: true),
            CurrentFlowTaskId = c.Int(nullable: false),
            IsActive = c.Boolean(nullable: false),
        })
    .PrimaryKey(t => t.FlowId);

CreateTable(
    "dbo.FlowTasks",
    c => new
        {
            FlowTaskId = c.Int(nullable: false, identity: true),
            FlowId = c.Int(nullable: false),
            Discription = c.String(nullable: false, maxLength: 25),
            Flow_FlowId = c.Int(),
        })
    .PrimaryKey(t => t.FlowTaskId)
    .ForeignKey("dbo.Flows", t => t.Flow_FlowId)
    .ForeignKey("dbo.Flows", t => t.FlowId)
    .Index(t => t.FlowId)
    .Index(t => t.Flow_FlowId);

The first thing that seems amiss here is the Flow_FlowId column that is created in the flow tasks.

When I run the following block of code in LinqPad I do not see the results I expect; A Flow and a FlowTask are created, but Flow.CurrentTaskId is null, and the off Flow_FlowId column is set to the same value as Flow.FlowId

var fi = new CompWalk.Data.FlowTask
{
    Discription = "Task 1",
};
var f = new CompWalk.Data.Flow {
    IsActive = true,
    CurrentFlowTask = fi,
    FlowTasks = new[] {
        fi
    }
};

Flows.Add(f);
SaveChanges();

This code was adapted from an almost identical question here, but is several years old so may no longer be applicable.

Is what I am attempting possible without doing a multiple inserts and saves? Also, what is causing the generation of the Flow_FlowId column?

Joe
  • 5,389
  • 7
  • 41
  • 63

1 Answers1

1

You have 2 different relationship types between Flow and FlowTask. You configured one-to-many relationship by adding FlowId to FlowTask and configured it so we have this line in migration

.ForeignKey("dbo.Flows", t => t.FlowId)

which is correct. And there is one-to-one relationship where you marked Flow as principal and therefore FlowTask is dependent. Entity Framework adds principal Id as foreign key to dependent entity to create such relationship. It's not possible to configure foreign key for one-to-one relationship with entity's property (like you did for one-to-many) in Entity Framework. And because of it framework generates foreign key for you and adds it to dependent entity (FlowTask).

.ForeignKey("dbo.Flows", t => t.Flow_FlowId)

And your public int CurrentFlowTaskId { get; set; } property is just a regular column and not a foreign key so indeed it's not set in your example. And Flow_FlowId is set to Flow.FlowId because it is foreign key.

If you want the one-to-one relationship foreign key be added to Flow just change this line

HasOptional(x => x.CurrentFlowTask)
    .WithOptionalPrincipal()
    .WillCascadeOnDelete(false);

to

HasOptional(x => x.CurrentFlowTask)
    .WithOptionalDependent()
    .WillCascadeOnDelete(false);

If you want to specify name of the key change it to following

HasOptional(x => x.CurrentFlowTask)
    .WithOptionalDependent()
    .Map(m => m.MapKey("KeyName"))
    .WillCascadeOnDelete(false);

But if you decide to add KeyName property to Flow entity, migration will yield an exception:

KeyName: Name: Each property name in a type must be unique. Property name 'KeyName' is already defined.

So you won't be able to access this value directly in code and I don't know how fix this. Haven't researched it yet. But I think it should be possible to configure this relationship as one-to-many and use it as one-to-one somehow but I'm not sure.

Alexander
  • 9,104
  • 1
  • 17
  • 41