Entity Framework 6 One To Many With Additional Navigation Property - entity-framework

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?

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.

Related

Entity Framework 6 - Many to Many Foreign Key Constraint Causing Multiple Cascade Paths Error

I have a many to many relationship defined as follows:
public class Ticket
{
public int Id { get; set; }
public ICollection<ApplicationUser> TicketSubscribers { get; set; }
}
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
public ICollection<Ticket> SubscriberTickets { get; set; }
}
Fluent api mappings as follows:
public ApplicationUserConfiguration()
{
HasMany<Ticket>(u => u.SubscriberTickets)
.WithMany(t => t.TicketSubscribers)
.Map(
ts =>
{
ts.MapLeftKey("UserId");
ts.MapRightKey("TicketId");
ts.ToTable("TicketSubscriber");
});
}
Migration is as follows:
CreateTable(
"dbo.TicketSubscriber",
c => new
{
UserId = c.String(nullable: false, maxLength: 128),
TicketId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserId, t.TicketId })
.ForeignKey("dbo.User", t => t.UserId, cascadeDelete: true)
.ForeignKey("dbo.Ticket", t => t.TicketId, cascadeDelete: true)
.Index(t => t.UserId)
.Index(t => t.TicketId);
When I run update database I get the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.TicketSubscriber_dbo.Ticket_TicketId' on table 'TicketSubscriber' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.
I dont know how you can specify cascade delete false on a many to many mapping?
Why would it generate this error on a many to many mapping. How do I fix this?

Entity Framework: one to zero or one relationship with foreign key on principal

I have a 1:0..1 relationship that I'd like to map with EF 6 using fluent API. The relation consists of a principal, which may or may not have a dependent. A dependent must always have a principal.
In the principal, I need to have access to the Id of the dependent.
My code looks like this:
public class Principal
{
public int Id {get; private set; }
public int? DependentId { get; private set; }
public virtual Dependent Dependent { get; private set; }
}
public class Dependent
{
public int Id { get; private set; }
public virtual Principal Principal { get; private set; }
}
My mapping looks like this:
public class PrincipalMap : EntityTypeConfiguration<Principal>
{
public PrincipalMap()
{
ToTable("PRINCIPALS");
HasKey(x => x.Id);
Property(x => x.Id)
.HasColumnName("PRINCIPALID")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.DependentId)
.HasColumnName("DEPENDENTID")
.IsOptional();
}
}
public class DependentMap : EntityTypeConfiguration<Dependent>
{
public DependentMap()
{
ToTable("DEPENDENTS");
HasKey(x => x.Id);
Property(x => x.Id)
.HasColumnName("DEPENDENTID")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.Principal).WithOptional(x => x.Dependent).Map(x => x.MapKey("PRINCIPALID")).WillCascadeOnDelete();
}
}
Which results in the following migration:
CreateTable(
"dbo.PRINCIPALS",
c => new
{
PRINCIPALID = c.Int(nullable: false, identity: true),
DEPENDENTID = c.Int(),
})
.PrimaryKey(t => t.PRINCIPALID);
CreateTable(
"dbo.DEPENDENTS",
c => new
{
DEPENDENTID = c.Int(nullable: false, identity: true),
PRINCIPALID = c.Int(nullable: false),
})
.PrimaryKey(t => t.DEPENDENTID)
.ForeignKey("dbo.PRINCIPALS", t => t.PRINCIPALID, cascadeDelete: true)
.Index(t => t.PRINCIPALID);
As you can see, the column DEPENDENTID is not a foreign key. When running the program and associating a dependent object to a principal, the DependentId property remains empty, i.e. EF does not recognize it to be related to the dependent itself.
What am I doing wrong?
In DependentMap you declared field DEPENDENTID as primary key of DEPENDENT table, database generated (identity) so it will never be a foreign key. You can't change it as you want (making it pointing to an entity of your choice).
Also, with EF (and E/R) you don't need two columns (one per table) to have a 1-0..1 relationship. You can have only one column (not nullable).
In your case this model should work:
public class Principal
{
public int Id { get; private set; }
public virtual Dependent Dependent { get; private set; }
}
public class Dependent
{
public int Id { get; private set; }
public virtual Principal Principal { get; private set; }
}
public class PrincipalMap : EntityTypeConfiguration<Principal>
{
public PrincipalMap()
{
ToTable("PRINCIPALS");
HasKey(x => x.Id);
Property(x => x.Id)
.HasColumnName("PRINCIPALID")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class DependentMap : EntityTypeConfiguration<Dependent>
{
public DependentMap()
{
ToTable("DEPENDENTS");
HasKey(x => x.Id);
Property(x => x.Id)
.HasColumnName("DEPENDENTID")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.Principal).WithOptional(x => x.Dependent).Map(x => x.MapKey("PRINCIPALID")).WillCascadeOnDelete();
}
}
In this case the table creation stataments (generated by EF provider) should be similar to
ExecuteNonQuery==========
CREATE TABLE [DEPENDENTS] (
[DEPENDENTID] int not null identity(1,1)
, [PRINCIPALID] int not null
);
ALTER TABLE [DEPENDENTS] ADD CONSTRAINT [PK_DEPENDENTS_204c4d57] PRIMARY KEY ([DEPENDENTID])
ExecuteNonQuery==========
CREATE TABLE [PRINCIPALS] (
[PRINCIPALID] int not null identity(1,1)
);
ALTER TABLE [PRINCIPALS] ADD CONSTRAINT [PK_PRINCIPALS_204c4d57] PRIMARY KEY ([PRINCIPALID])
ExecuteNonQuery==========
CREATE INDEX [IX_PRINCIPALID] ON [DEPENDENTS] ([PRINCIPALID])
ExecuteNonQuery==========
ALTER TABLE [DEPENDENTS] ADD CONSTRAINT [FK_DEPENDENTS_PRINCIPALS_PRINCIPALID] FOREIGN KEY ([PRINCIPALID]) REFERENCES [PRINCIPALS] ([PRINCIPALID])
(I omitted on cascade delete but should be clear as well).
The E/R model is in normal form (and is the only that works with EF).
BTW, if you access to Principal.Dependent property EF will generate a query similar to selected * from dependent where PRINCIPALID = <principal_id> where is the id of the principal entity so it really works.
Now, about your requirements, to access to Dependent.Id from Principal the only way is dependentId = Principal.Dependent.Id (or, better, dependentId = Principal.Dependent == null ? null : Principal.Dependent.Id).
What to do if you REALLY WANT a field for the foreign key on PRINCIPAL that refers to DEPENDENT table?
This model is not in normal form so EF will not handle it (also with DBMS you need to write triggers to handle it).
I mean, in R-DBMS there is not a constraint where you can specify that if a column DEPENDENT.PRINCIPALID refers to a PRINCIPAL also a column PRINCIPAL.DEPENDENTID should refers to the original DEPENDENT.
What you need to do in this case is to handle PRINCIPAL.DEPENDENTID yourself (i.e. the Principal entity must have a DEPENDENTID property that you must handle by yourself and is not used by EF during navigation).
Yes, that is tricky and an EF bug IMO. The workaround I have used is a pseudo 1:M:
HasRequired(x => x.Principal)
.WithMany()
.HasForeignKey(x => x.DependentId);
.WillCascadeOnDelete();
http://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations

code first migration add one to many relationship

I currently have a "server" entity, defined as such :
public class EntityServer
{
public int Id { get; set; }
public string Name { get; set; }
}
I wanted to add a new "Host" entity, defined as such :
public class EntityHost
{
public int Id { get; set; }
public string Name { get; set; }
public string PublicIP { get; set; }
private ICollection<EntityServer> _servers;
public virtual ICollection<EntityServer> Servers
{
get { return _servers ?? (_servers = new HashSet<EntityServer>()); }
set { _servers = value; }
}
}
So i added
public virtual EntityHost Host { get; set; }
to my server entity to link those entities with a one to many relationship
modelBuilder.Entity<EntityHost>()
.HasMany<EntityServer>(x => x.Servers)
.WithRequired(x => x.Host);
And generated a migration acordingly :
public partial class MultiHosts : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.EntityHosts",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
PublicIP = c.String(),
})
.PrimaryKey(t => t.Id);
AddColumn("dbo.EntityServers", "Host_Id", c => c.Int(nullable: false));
CreateIndex("dbo.EntityServers", "Host_Id");
AddForeignKey("dbo.EntityServers", "Host_Id", "dbo.EntityHosts", "Id", cascadeDelete: true);
}
public override void Down()
{
DropForeignKey("dbo.EntityServers", "Host_Id", "dbo.EntityHosts");
DropIndex("dbo.EntityServers", new[] { "Host_Id" });
DropColumn("dbo.EntityServers", "Host_Id");
DropTable("dbo.EntityHosts");
}
}
I've got some troubble setting a code first migration to add all it together as it outpout me a foreign key violation error when i try to access the context (which i understand as the server entity isn't linked to a host, as required by the model, because the hosts table is empty and I can't access the hosts entities to add one because of the FK violation ....)
So, my question is : how should I insert a default host entites for the existings server ?
As a trick you could first set the Server as Optional
modelBuilder.Entity<EntityHost>()
.HasOptional(x=>x.Server)
.WitMany(x => x.Hosts);
Run
Add-Migrations set_server_optional
update-Database
Update your Database and then change the Server as Required
modelBuilder.Entity<EntityHost>()
.HasRequired(x=>x.Server)
.WithMany(x => x.Hosts);
And finally
Add-Migrations set_server_required
update-Database

Modeling a composite key of foreign keys (modelled as entity references) in EF6

I am coming from nHibernate and am trying to create an entity that has a 2 column composite key where both columns are also foreign keys.
For example I have a UserRole table that is (UserId Guid, RoleId Guid). I want to model this as
public class UserRole
{
public User User { get; set; }
public Role Role { get; set; }
}
EF doesn't seem to like this idea though. It seems to want me to also add Guid UserId {get;set;} and Guid RoleId { get; set; }. I managed to resolve this for the handling the FK part by defining the model in the dbcontext like so:
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.Role)
.WithRequiredPrincipal()
.Map(x => x.MapKey("RoleId"));
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.User)
.WithRequiredPrincipal()
.Map(x => x.MapKey("UserId"));
Which I hope I can turn into a convention. However when I tried to do this too:
modelBuilder.Entity<UserRole>().HasKey(x => new { x.User, x.Role });
it craps out at runtime with:
The property 'User' cannot be used as a key property on the entity 'Paroxysm.Domain.UserRole' because the property type is not a valid key type. Only scalar types, string and byte[] are supported key types.
FYI this is done in nHibernate byCode mapping like this (slightly different example):
public class ProjectUserProfileMap : ClassMap<ProjectUserProfile>
{
public ProjectUserProfileMap()
{
CompositeId()
.KeyReference(x => x.User, "UserId")
.KeyReference(x => x.Project, "ProjectId");
ReadOnly();
References(x => x.User, "UserId");
References(x => x.Project, "ProjectId");
Map(x => x.IsActive);
Map(x => x.ActivatedUtcDate).Not.Nullable().CustomType<NHibernate.Type.UtcDateTimeType>();
Map(x => x.InvitedUtcDate).Not.Nullable().CustomType<NHibernate.Type.UtcDateTimeType>();
Table("ProjectUserProfile");
}
}
So easy! Incidentally that little CustomType UTC behaviour doesn't seem to be supported by EF either.
Problem is not actually related to the fact that I have a composite key but having a single column PK which is also an FK would be a weird case (1:1 rel).
So I guess I want to know definitely if this can or cannot be done in EF6. The error message certainly indicates its not doable. Can someone confirm?
You could achieve this but only if you add to UserRole 2 foreign key properties: RoleId and UserId. This is because HasKey do not offer any mapping functionality - it can be defined only on properties existing in entity classes. It seems EF enforces that all Primary Key columns are always defined as concrete properties in classes wheres foreign key columns may not have corresponding properies defined. So to achieve what you want you need to define UserRole like this:
public class UserRole
{
public User User { get; set; }
public int UserId { get; set; }
public Role Role { get; set; }
public int RoleId { get; set; }
}
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.Role)
.WithRequiredPrincipal()
.HasForeignKey(x => x.RoleId);
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.User)
.WithRequiredPrincipal()
.HasForeignKey(x => x.UserId);
modelBuilder.Entity<UserRole>().HasKey(x => new { x.UserId, x.RoleId });
The exact situation as you posted you might alternatively achieve by many-to-many relationship:
modelBuilder.Entity<User>()
.HasMany(x => x.Roles)
.WithMany()
.Map(m =>
{
m.ToTable("UserRole");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
With this you would achieve UserRole table with primary key defined on UserId and RoleId.

EF5/Code First relations work at database level but not in the assembly

I'm transitioning from nHibernate to EF5 and am having problems with mapping relations. Working with a conventional one-to-many relation let's call it Instructor/Course:
public class Course
{
public Course()
{
Instructor = new Instructor();
CourseFiles = new List<CourseFile>();
}
public int Id { get; set; }
public string Description { get; set; }
public string Title { get; set; }
public int InstructorId { get; set; }
public virtual Instructor Instructor { get; set; }
public virtual ICollection<CourseFile> CourseFiles { get; set; }
}
public class CourseMap : EntityTypeConfiguration<Course>
{
public CourseMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Description)
.IsRequired();
this.Property(t => t.Title)
.IsRequired()
.HasMaxLength(125);
// Table & Column Mappings
this.ToTable("Course");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Description).HasColumnName("Description");
this.Property(t => t.Title).HasColumnName("Title");
this.Property(t => t.InstructorId).HasColumnName("InstructorId");
// Relationships
this.HasRequired(t => t.Instructor)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.InstructorId);
}
}
public partial class Instructor
{
public Instructor()
{
this.Courses = new List<Course>();
}
public int Id { get; set; }
public string Biography { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class InstructorMap : EntityTypeConfiguration<Instructor>
{
public InstructorMap()
{
// Primary Key
this.HasKey(t => t.Id);
this.Property(t => t.Biography)
.HasMaxLength(140);
// Table & Column Mappings
this.ToTable("Instructor");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Biography).HasColumnName("Biography");
//the mapping of this relation _has to be where the problem is
// really seems like this should create the required plumbing but no joy
this.HasMany(w => w.Webinars)
.WithRequired()
.HasForeignKey(w => w.PresenterId);
}
}
}
The above is the POCO generated by the Reverse Engineer tool where I've used as a starting point, the db that nHibernate's been using for the last few years. For the most part things lined up really well. After minor adjustments for PKey names I've been able to seed the new db with nHibernate's data and the same joins are working against both dbs.
E.G. this works the same returns correct values:
SELECT C.Title, C.Id, C.InstructorId, I.Id
FROM dbo.Course C
INNER JOIN dbo.Instructor I ON I.Id = C.InstructorId
...
And even within the assembly, this linq query pulls correctly against the db:
var query = from Course in _ctx.Courses
where Course.Instructor.Id == InstructorId
select Course;
So I must have lots of the ducks lined up correctly. However, when attempting to access the Instructor entity from _within the Course entity, as is typical at the View:
#foreach (var course in Model)
{
<div>#course.Title - Instructor.ID is all zeros: #course.Instructor.Id - FK is correct: #course.InstructorId</div>
}
outputs:
My Correct Course Title - Instructor.ID is all zeros: 0 - FK is correct: 555
Title From Diff Instructor - Instructor.ID is all zeros: 0 - FK is correct: 333
and so on for all courses.
I'm not taking explicit action to Dispose anything. I set a breakpoint at the start of the ForEach inside the view and don't see 'context' in the Locals window. Breaking inside the Controller the Instructor node (of context) shows a line for each and every presenter in the db all properly seeded except the first 10 (the Repository is using '.Take(10)'. Those first 10 Presenter lines (in Presenter|Local) are all zeroed out. So clearly, something's up with the Constructor in the POCO.