Extra Foreign Key in Code First Migrations - c#

I'm kinda new to Entity Framework and Code First Migration so I hope this is an easy question to answer. I am trying to create a one to one relationship between ApplicationUser (from ASP.NET identity) and Member. I have a Member class:
public class Member
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Address Address { get; set; }
public UserStatus Status { get; set; }
public DateTime CreateDate { get; set; }
public virtual string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
and an ApplicationUserClass:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
}
public virtual Member Member { get; set; }
}
In my DBContext (inherits IdentityDbContext)I have the following configuration:
modelBuilder.Entity<ApplicationUser>()
.HasOptional(t => t.Member).WithOptionalPrincipal();
base.OnModelCreating(modelBuilder);
When I run the code first migration, I'm getting this:
CreateTable(
"dbo.Members",
c => new
{
ID = c.Int(nullable: false, identity: true),
FirstName = c.String(),
LastName = c.String(),
Status = c.Int(nullable: false),
CreateDate = c.DateTime(nullable: false),
ApplicationUserID = c.String(maxLength: 128),
Address_ID = c.Int(),
ApplicationUser_Id = c.String(maxLength: 128),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Addresses", t => t.Address_ID)
.ForeignKey("dbo.AspNetUsers", t => t.ApplicationUser_Id)
.ForeignKey("dbo.AspNetUsers", t => t.ApplicationUserID)
.Index(t => t.ApplicationUserID)
.Index(t => t.Address_ID)
.Index(t => t.ApplicationUser_Id);
Notice that I have 2 foreign keys, ApplicationUserID and ApplicationUser_Id. I want to try to do everything using FluentAPI (i.e. not Data Annotations). How would I configure it so that EF uses ApplicationUserID, the string ID I have in my class? I thought that Class+ID was the convention, so why is it creating another foreign key?

I believe you should update your configuration this way:
modelBuilder.Entity<Member>()
.HasOptional(x => x.ApplicationUser)
.WithMany()
.HasForeignKey(x => x.ApplicationUserID);
This is the way EntityFramework treats one-to-one relationships, you have to map it that way and introduce UNIQUE constraints over your DB table.
More information about this case is here: http://weblogs.asp.net/manavi/associations-in-ef-code-first-ctp5-part-3-one-to-one-foreign-key-associations
Here's the quote from the link:
The reason is simple: Code First (and EF in general) does not natively
support one-to-one foreign key associations. In fact, EF does not
support any association scenario that involves unique constraints at
all. Fortunately, in this case we don’t care what’s on the target side
of the association, so we can treat it like a to-one association
without the many part. All we want is to express “This entity (User)
has a property that is a reference to an instance of another entity
(Address)” and use a foreign key field to represent that relationship.
Basically EF still thinks that the relationship is many-to-one. This
is a workaround for the current EF limitation which comes with two
consequences: First, EF won't create any additional constraint for us
to enforces this relationship as a one to one, we need to manually
create it ourselves. The second limitation that this lack of support
impose to us is more important: one to one foreign key associations
cannot be bidirectional (i.e. we cannot define a User property on the
Address class).

Related

EF6 code first one to zero or one with Predefined FK property

am working on some legacy code , would to add new table to the DB
and it would have two one to zero or one relation ship and I need "for some reason" The ForeignKeys in the new table to be defined as properties and The code :
public class EconomyInfo : Entity
{
/*other props*/
[ForeignKey("LogoImage")]
[Required]
public int LogoImage_Id { get; set; }
public virtual Image LogoImage { get; set; }
[ForeignKey("Organization")]
[Required]
public int Organization_Id { get; set; }
public virtual Organization Organization { get; set; }
}
public class Image : Entity
{
/*other props*/
public virtual EconomyInfo Economyinfo { get; set; }
}
public class Organization : Entity
{
/*other props*/
public virtual EconomyInfo EconomyInfo { get; set; }
}
public class Entity
{
public int Id { get; set; }
}
Like This EF don't generate migration and give the error
EconomyInfo_LogoImage_Source: : Multiplicity is not valid in Role 'EconomyInfo_LogoImage_Source' in relationship 'EconomyInfo_LogoImage'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
and the same for the other navigation property
and if i add those to lines in the the context class:
modelBuilder.Entity<Organization>().HasOptional(o => o.EconomyInfo).WithOptionalPrincipal();
modelBuilder.Entity<Image>().HasOptional(i => i.Economyinfo).WithOptionalPrincipal();
the migration will be generated without errors but it will be strange like :
public override void Up()
{
CreateTable(
"dbo.EconomyInfoes",
c => new
{
Id = c.Int(nullable: false, identity: true),
LogoImage_Id = c.Int(nullable: false),
Organization_Id = c.Int(nullable: false),
Image_Id = c.Int(),
Organization_Id1 = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Images", t => t.LogoImage_Id, cascadeDelete: true)
.ForeignKey("dbo.Organizations", t => t.Organization_Id)
.ForeignKey("dbo.Images", t => t.Image_Id)
.ForeignKey("dbo.Organizations", t => t.Organization_Id1)
.Index(t => t.LogoImage_Id)
.Index(t => t.Organization_Id)
.Index(t => t.Image_Id)
.Index(t => t.Organization_Id1);
}
i know that i could edit the generated EF migration code to make it specify my needs , but there is no point of wasting hours dealing with EF and then give up and work around it , plus it won't be so practical to work with it later by my colleagues ,thanks in advance
I've just given up and decided to go with the work around as write my own migration for my new entity and tell the context to ignore it from the upcoming generated migration.
So my solution is this:
In the context class :
// Regarding unPossible configuration to make pre defined property as foreign key in one to zero or one relationship
// we wrote our migration and tell the context to ignore our model and its navigation property from other model classes
modelBuilder.Entity<Organization>().Ignore(o => o.EconomyInfo);
modelBuilder.Entity<Image>().Ignore(i => i.Economyinfo);
modelBuilder.Ignore<EconomyInfo>();
Organization class :
public virtual EconomyInfo EconomyInfo { get; set; }
Image class :
public virtual EconomyInfo Economyinfo { get; set; }
Manual written migration :
using System;
using System.Data.Entity.Migrations;
public partial class EconomyInfo : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.EconomyInfoes",
c => new
{
Id = c.Int(nullable: false, identity: true),
LogoImage_Id = c.Int(nullable: false),
Organization_Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Images", t => t.LogoImage_Id)
.ForeignKey("dbo.Organizations", t => t.Organization_Id)
.Index(t => t.LogoImage_Id)
.Index(t => t.Organization_Id);
}
public override void Down()
{
DropForeignKey("dbo.EconomyInfoes", "Organization_Id", "dbo.Organizations");
DropForeignKey("dbo.EconomyInfoes", "LogoImage_Id", "dbo.Images");
DropIndex("dbo.EconomyInfoes", new[] { "Organization_Id" });
DropIndex("dbo.EconomyInfoes", new[] { "LogoImage_Id" });
DropTable("dbo.EconomyInfoes");
}
}
You may refer to this question also for more info How do I specify the foreign key in a one-to-one/zero relationship?
Note: I applied my written migration to database works fine, try to generate new migration, also works as expected, but not tested working with my new model class from code yet. Hopefully nothing will be messed later,

Mapping reference property to abstract parent

I have a complex object hierarchy in an enterprise application. I'll try and keep it simple, and abstract, yet still representative of what I'm dealing with.
My project deals with several styles of the same type of object. For this, we have implemented the TPT structure for our entity objects:
public abstract class BaseWidget {
public int Id { get; set; }
// etc...
}
// About a dozen concrete implementations already exist and work great!
public class ExistingWidget : BaseWidget {
// Other properties
}
Now I have a new type that I'm doing. We have common properties on the object, but there are a few different sets of details that are required depending on the sub type. For this, I set up TPH, as the properties on that type are the same across all subtypes. The only difference is which details objects are required.
public abstract NewWidgetBase : BaseWidget {
public int EmployeeNumber { get; set; }
public DateTime EffectiveDate { get; set; }
}
public NewWidgetA : NewWidgetBase {
}
public NewWidgetB : NewWidgetBase {
}
I have this mapped in my DbContext like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<NewWidgetBase>()
.Map<NewWidgetA>(w => w.Requires("Discriminator").HasValue("a"))
.Map<NewWidgetB>(w => w.Requires("Discriminator).HasValue("b"));
At this point, I have used an integration test and successfully checked that I can save to both tables.
Now, I want to add in the details:
public class FooDetails {
public int Id { get; set; }
public int NewWidgetId { get; set; }
// ...
[ForeignKey(nameof(NewWidgetId))]
public NewWidgetBase NewWidget { get; set; }
}
public class BarDetails {
public int Id { get; set; }
public int NewWidgetId { get; set; }
// ...
[ForeignKey(nameof(NewWidgetId))]
public NewWidgetBase NewWidget { get; set; }
}
I then add those reference properties to my appropriate NewWidget objects.
public class NewWidgetA {
// ...
public FooDetails Foo { get; set; }
}
public class NewWidgetB {
// ...
public FooDetails Foo { get; set; }
public BarDetails Bar { get; set; }
}
I tried just executing this, assuming that the typical mapping would work, and got the following error:
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.Entity.Core.UpdateException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
With that, I understood that it doesn't have the correct Relationship directions and keys mapped. So I went to explicitly set it within the DbContext again:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredDependent();
However, that gives me the error:
System.InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'WidgetId'.
I looked at a "some other" "questions", and none of those answers helped me.
As a last ditch effort, I tried using the overload for .WithRequiredDependent() which takes a Func. However, because it isn't the exact same type as I'm mapping because I have the property as the abstract base, it complains. Therefore, I try casting it like so:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredDependent(f => (NewWidgetA)f.Widget);
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Foo)
.WithRequiredDependent(f => (NewWidgetB).Widget);
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Bar)
.WithRequiredDependent(b => (NewWidgetB).Widget);
However, this also gives an error:
The ForeignKeyAttribute on property 'Widget' on type '...Foo' is not valid. The foreign key name 'WidgetId' was not found on the dependent type 'NewWidgetA'. The Name value should be a comma separated list of foreign key property names.
This is leading me to believe that I'm unable to do what I want to do with having abstract properties. Is there a way to map this relationship that I'm missing? I don't want to have a specific reference property for each as I know there are more types coming within a month or two, and the list of properties will get unwieldy.
It's possible, but only with unidirectional (with navigation property only at Widget side) one-to-one Shared Primary Key Association, where the Widget side is the principal and the Details side is the dependent.
Start by removing the navigation and FK properties from Details entities:
public class FooDetails {
public int Id { get; set; }
// ...
}
public class BarDetails {
public int Id { get; set; }
// ...
}
and use the following fluent configuration:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredPrincipal();
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Foo)
.WithRequiredPrincipal();
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Bar)
.WithRequiredPrincipal();
Note the WithRequiredPrincipal() call. It's telling EF that (1) the Widget is the principal and (2) there is no navigation property from Details to Widget.
The resulting database schema is something like this:
CreateTable(
"dbo.BaseWidget",
c => new
{
Id = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.ExistingWidget",
c => new
{
Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.BaseWidget", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.NewWidgetBase",
c => new
{
Id = c.Int(nullable: false),
EmployeeNumber = c.Int(nullable: false),
EffectiveDate = c.DateTime(nullable: false),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.BaseWidget", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.FooDetails",
c => new
{
Id = c.Int(nullable: false),
Data = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.NewWidgetBase", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.BarDetails",
c => new
{
Id = c.Int(nullable: false),
Data = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.NewWidgetBase", t => t.Id)
.Index(t => t.Id);

EntityType ''IdentityUserRole' has no key defined error

Working with entity framework code first.
Trying to use the IdentityRole id as a foreign key to a custom class.
the relationship is correct but when I try to add data to my custom table i get this error.
EntityType 'IdentityUserRole' has no key defined. Define the key for
this EntityType. IdentityUserRoles: EntityType: EntitySet
'IdentityUserRoles' is based on type 'IdentityUserRole' that has no
keys defined.
Code:
Custom table/class
public class UserRoleAccess
{
[Key]
public long UserRoleAccessId { get; set; }
public string UserRoleId { get; set; }
public virtual ApplicationRole ApplicationRole { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public int UserRoleAccessType { get; set; }
}
IdentityRole:
public class ApplicationRole : IdentityRole
{
public virtual ICollection<UserRoleAccess> UserRoleAccess { get; set; }
}
Bindings:
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<IdentityUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<ApplicationUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<ApplicationRole>().ToTable("Roles");
modelBuilder.Entity<UserRoleAccess>().ToTable("UserRoleAccess");
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users);
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users)
.HasForeignKey(p => p.PersonId);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<ApplicationRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}
Relationship in db(correct):
Tried creating an id property and setting a [Key] attribute but it did not work:
public class ApplicationRole : IdentityRole
{
[Key]
public string Id { get; set; }
public virtual ICollection<UserRoleAccess> UserRoleAccess { get; set; }
}
Run this code when adding data:
public static void CreateUserRoleAccess(string name, int type, string id)
{
using (var context = new UserRoleAccessContext())
{
var userRoleAccess = new UserRoleAccess();
userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
userRoleAccess.UserRoleId = id;
userRoleAccess.IsActive = true;
userRoleAccess.UserRoleAccessType = type;
userRoleAccess.Name = name;
context.UserRoleAccess.Add(userRoleAccess);
context.SaveChanges();
}
}
What am I missing?
EDIT:
IdentityRole class:
IdentityRole
So, you are using MVC 5 with ASP.NET Identity 2.0. I guess your project has the Nuget package for Microsoft.AspNet.Identity.EntityFramework and the right tables have been created in your database.
Perhaps your problem is because you are redefining the mappings for the standard Identity tables. Do you really need to map explicitly the table names and keys? I would try to keep the original mappings, and only add explicit mappings for your custom entities and fields.
I think you don't need the ToTable mappings, as the base entities have a mapping somewhere in its code, and your entities extend them.
A couple of questions:
What is the meaning of UserRoleAccess.UserRoleId? Is it a user id, or a role id? If it is meant to contain the UserRole id, it is a composed key, not a single value, so it is not likely that a String property would do. Perhaps you should rename it.
What do you want to do exactly in the method CreateUserRoleAccess: adding a new UserRoleAccess for a role and user that already exist and are already related (that is, the UserRole relation already exists)? Or either adding a new UserRole and UserRoleAccess for a user and role that already exist but are not related? I assume you do not want to create the Role or the User at this point, they already exist, am I wrong?
Anyway, in your CreateUserRoleAccess method, in this line:
userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
Unless you are creating a new Role in this same moment you should do a Find or other Linq query and get the existing Role from the context. Perhaps just changing this would make your code work:
//userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
userRoleAccess.ApplicationRole = context.Roles.Find(id) as ApplicationRole;
I created a MVC project and tried your code with several changes:
I did not include any explicit EF bindings or mappings, just to try with the original Identity model and see if it works.
I extended Identity DbContext just adding a DbSet for UserRoleAccess:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public virtual IDbSet<UserRoleAccess> UserRoleAccesses { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
After adding your custom entities ApplicationRole and UserRoleAccess, generating a migration and updating the database I got the same UserRoleAccess table than you, but also a new column Discriminator in the Roles table:
This means EF is using TPH (Table Per Hierarchy) when you extend the IdentityRole entity with your ApplicationRole entity. They share the table. This is implicit, no need to write bindings (in fact, unneeded explicit bindings tend to interfere).
So, if your roles table is missing this field this can be a probable cause for you error.
I run the MVC web, and created a new user with the Register link in the page header. I run the following code (by adding it to the index controller), and it worked:
public void CreateUserRoleAccess(string name, int type, string id)
{
//Insert role (run only once)
using (var context = new ApplicationDbContext())
{
var role = new ApplicationRole { Id = id, Name = "Role1" };
var user = context.Users.FirstOrDefault<ApplicationUser>();
//user.Roles.Add(role);
role.Users.Add(new IdentityUserRole { RoleId = role.Id, UserId = user.Id });
context.Roles.Add(role);
context.SaveChanges();
}
using (var context = new ApplicationDbContext())
{
var userRoleAccess = new UserRoleAccess();
//userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
userRoleAccess.ApplicationRole = context.Roles.Find(id) as ApplicationRole;
//Is this a userId or a roleId? Does not seem OK to assign id param here
userRoleAccess.UserRoleId = id;
userRoleAccess.IsActive = true;
userRoleAccess.UserRoleAccessType = type;
userRoleAccess.Name = name;
context.UserRoleAccesses.Add(userRoleAccess);
context.SaveChanges();
}
}
Note that I create first a new Role with a different context scope, to make sure the Role exists when I get to the interesting code. I use that role later getting it from the context.
Hope this helps. I can send you the whole solution code if you want.
EDIT - UserRoleAccess table:
CreateTable(
"dbo.UserRoleAccesses",
c => new
{
UserRoleAccessId = c.Long(nullable: false, identity: true),
UserRoleId = c.String(),
IsActive = c.Boolean(nullable: false),
Name = c.String(),
UserRoleAccessType = c.Int(nullable: false),
ApplicationRole_Id = c.String(maxLength: 128),
})
.PrimaryKey(t => t.UserRoleAccessId)
.ForeignKey("dbo.AspNetRoles", t => t.ApplicationRole_Id)
.Index(t => t.ApplicationRole_Id);
Useful links:
Introduction to ASP.NET Identity - I created the simple MVC solution using this info.
ASP.NET Identity, extract from book Pro ASP.NET MVC 5

EF fluent API c# cascade delete issue

I am having problems configuring a model to not cascade delete.
The modeL.
public class Hit
{
public int Id { get; set; }
// Foreign Key
public int AccountId { get; set; }
// Foreign Key
public int LeadId { get; set; }
//navigation properties
[ForeignKey("LeadId")]
public Lead lead { get; set; }
//navigation properties
[ForeignKey("AccountId")]
public Account account { get; set; }
}
Fluent API:
modelBuilder.Entity<Hit>()
.HasRequired(t => t.lead)
.WithMany()
.HasForeignKey(t => t.LeadId)
.WillCascadeOnDelete(false);
When I try to migrate it, i get this in create table on Hit:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
.ForeignKey("dbo.Leads", t => t.Lead_Id)
.ForeignKey("dbo.Leads", t => t.LeadId, cascadeDelete: true)
What I want to achieve for both leads and accounts is this in the migration script:
.ForeignKey("dbo.Leads", t => t.LeadId)
How should I go about doing this? Its prbably easy, but I am new to all this:(
EDIT 1:
I added this to the fluent API:
modelBuilder.Entity<Lead>().HasMany(i => i.Hits).WithOptional().HasForeignKey(s=> s.LeadId).WillCascadeOnDelete(false);
Which remove cascadelete from: .ForeignKey("dbo.Leads", t => t.LeadId)
However, I also want to remove it from:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
The easy solution would be to remove it from the migration script. But I would like how to do this with FLuent API or other methods.
Any ideas?
Like I already said in the comments above, if you use Fluent API for mapping your FKs remove the [ForeignKey] attributes from your navigation properties.
You can then do non-cascading mappings like this:
modelBuilder.Entity<Hit>().HasOptional(m => m.account).WithMany().HasForeignKey(m
=> m.AccountId).WillCascadeOnDelete(false);
Finally you have to check if your FK columns should be optional or required, for optional mappings change your FK properties AccountId and LeadId to data type int?, otherwise you have to change the HasOptional() part of your FK mappings to HasRequired().
Define your model like so:
public class Hit
{
public int Id { get; set; }
public virtual Lead Lead { get; set; }
public virtual Account Account { get; set; }
}
The virtual keyword will let Entity Framework know you are defining a foreign key and will match on Id automatically.
Remove the HasRequired(t => t.lead).WithMany() part from Fluent API. If a property is required it cannot exist without it and will cascade on delete.

Incomplete EF code-first cascade delete on many to many relationship

I have a PhoneNumber entity which I'd like to reference across multiple entities. For example, a Contact entity that has many PhoneNumbers, and a Business entity with one PhoneNumber.
public class PhoneNumber
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Number { get; set; }
}
public class Contact
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
}
public class Business
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("PhoneNumber")]
public int PhoneNumberId { get; set; }
public virtual PhoneNumber PhoneNumber { get; set; }
}
I've setup Contact and Business so that they have one way navigation properties. Also, the phone numbers are optional. I've also setup the many relationship for Contact to prevent EF from adding a Contact_Id column when I add a migration. The mapping is as follows (note WithMany() is used since PhoneNumber doesn't have a nav property back to Contact):
modelBuilder.Entity<Contact>().HasMany(r => r.PhoneNumbers).WithMany()
.Map(x => x.MapLeftKey("ContactId").MapRightKey("PhoneId"));
When I add a Contact with multiple phone numbers it gets added fine. There are records for the Contact, the PhoneNumbers table, and the ContactPhoneNumbers link table.
However, the issue I'm struggling with is when I delete a contact. EF correctly deletes the entry in the ContactPhoneNumbers link table, and the Contact entry, but it doesn't delete the entries from the PhoneNumbers table. I've seen examples of mapping with modelBuilder where WillCascadeOnDelete(true) is used, but that option isn't available when using WithMany().
What do I need to do to get that type of cascade delete working correctly? Is it possible with this setup? Or will I need to have a separate PhoneNumbers table for each entity (Contact and Business) to setup a relationship where the respective PhoneNumber table uses a FK (eg., Contact_Id)?
I'm fairly new to EF so any suggestions are welcome. I might be going about this entirely wrong.
EDIT: here's the related migration code...
CreateTable(
"dbo.PhoneNumbers",
c => new
{
Id = c.Int(nullable: false, identity: true),
Number = c.String()
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Contacts",
c => new
{
Id = c.Int(nullable: false, identity: true)
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.ContactPhoneNumbers",
c => new
{
ContactId = c.Int(nullable: false),
PhoneId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.ContactId, t.PhoneId })
.ForeignKey("dbo.Contacts", t => t.ContactId, cascadeDelete: true)
.ForeignKey("dbo.PhoneNumbers", t => t.PhoneId, cascadeDelete: true)
.Index(t => t.ContactId)
.Index(t => t.PhoneId);
CreateTable(
"dbo.Business",
c => new
{
Id = c.Int(nullable: false),
PhoneNumberId = c.Int(nullable: false)
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.PhoneNumbers", t => t.PhoneNumberId, cascadeDelete: true)
.Index(t => t.Id)
.Index(t => t.PhoneNumberId);
In order for the cascading delete to work records that are going to be cascaded must have a foreign key back to the record being deleted. So in your example, You delete a Contact record. Because their is a foreign key from ContactPhoneNumber to Contact, the cascade works. Since there is no foreign key from PhoneNumber to ContactPhoneNumber, (the foreign key goes the other way) the cascade does not continue.
This is because you defined the relationship as a many to many. If you think about trying to perform a cascading delete on your model as you would like, If a ContactPhoneNumber is deleted and then its associated PhoneNumbers are deleted, there could now be other ContactPhoneNumbers that that don't have a valid PhoneNumber (Because there can be many ContactPhoneNumbers to one PhoneNumber). Now these would need to be deleted and this process would continue. Databases don't like this cyclical cascading.
It is not entirely clear to me why you need the many to many relationship, If you truly need it you will not be able to perform a cascade on delete. If you can make your relationship:
1 Contact - * ContactPhoneNumber 1- * PhoneNumber, then you could configure the cascade to work like you want it too.

Categories

Resources