Cascade deleting with EntityFramework - entity-framework

Have listed classes in model(simplified):
public partial class Category
{
public Category()
{
Topics = new HashSet<Topic>();
}
public Guid Id { get; set; }
public Guid? Category_Id { get; set; }//id for Parent category
public virtual ICollection<Topic> Topics { get; set; }
}
public partial class Topic
{
public Topic()
{
Posts = new HashSet<Post>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public Guid Category_Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual Category Category { get; set; }
}
Model here:
public partial class EntityModel : DbContext
{
public EntityModel()
: base("name=EntityModelContext")
{
Configuration.LazyLoadingEnabled = true;
Configuration.AutoDetectChangesEnabled = true;
Debug.WriteLine("Context Created");
}
public virtual DbSet<Category> Category { get; set; }
public virtual DbSet<Post> Post { get; set; }
public virtual DbSet<Topic> Topic { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.HasMany(e => e.Topics)
.WithRequired(e => e.Category)
.HasForeignKey(e => e.Category_Id)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Topic>()
.HasMany(e => e.Posts)
.WithRequired(e => e.Topic)
.HasForeignKey(e => e.Topic_Id)
.WillCascadeOnDelete(true);
}
}
So problem:
when i trying to delete sub category (that have NOT NULL Category_Id) => delete successful, but when i need to delete root category(Category_Id = NULL) there is an exeption: The DELETE statement conflicted with the REFERENCE constraint “FK_Topic_Category”.
Yes, I can delete reffered topics in the root category before and then delete category. But want to know why in root case cascade delete not work.

ok, just added ON DELETE CASCADE to children entity's FK in DB annotation.

Related

Self referencing loop detected for property with type

I have 3 instances, Author, EventInstance, and Comments. An author can write a comment. People (authors) can add comments to an EventInstance. Here's the model. I keep getting the following error.
public class Author
{
public Guid Id { get; set; }
//..
public List<EventInstance> Events { get; set; }
= new List<EventInstance>();
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
//..
public Author Author { get; set; }
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class Comment
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
public Author Author { get; set; }
}
I've tried several ways to fix but nothing seems to be working. Even a simple query like:
var list = await db.Events
.Include(e => e.Author)
.ToListAsync();
Thank for helping.
EDIT
Here's my context class
public class EventContext : DbContext
{
public DbSet<EventInstance> Events { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Author> Authors { get; set; }
public EventContext(DbContextOptions<EventContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<EventInstance>()
.HasOne(e => e.Author)
.WithMany(a => a.Events)
.HasForeignKey(e => e.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Event)
.WithMany(e => e.Comments)
.HasForeignKey(c => c.EventId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Author)
.WithMany(a => a.Comments)
.HasForeignKey(c => c.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
}
I'm using a Factory for add and run migrations
public class EventContextFactory : IDesignTimeDbContextFactory<EventContext>
{
public EventContext CreateDbContext(string[] args)
{
string connectionString =
Environment.GetEnvironmentVariable("SqlConnectionString");
var optionsBuilder = new DbContextOptionsBuilder<EventContext>();
optionsBuilder.UseSqlServer(connectionString);
return new EventContext(optionsBuilder.Options);
}
}
And this is the startup class
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
builder.Services.AddDbContext<EventContext>(
options => options.UseSqlServer(connectionString));
builder.Services.AddScoped<IEventService, EventService>();
}
}
I hope all the relevant pieces have been pasted here.
You should consider normalizing the structure to avoid the double referencing. For instance, if Authors have a collection of event instances and event instances have a list of comments then authors can get their comments through event instances.
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
[ForeignKey("Author")]
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
[ForeignKey("Event")]
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
}
Then to get the Comments for a particular Author:
var commentsForAuthor = context.Authors
.Where(x => x.Id == authorId)
.SelectMany(x => x.Events.SelectMany(e => e.Comments))
.ToList();
Alternatively, if Comments can be treated as a top-level entity (/w DbSet)
var commentsForAuthor = context.Comments
.Where(x => x.EventInstance.AuthorId == authorId)
.ToList();
Typically you would use Projection to get details about a comment, it's associated event, and author. (Using Select) This can navigate through the relational structure to get the appropriate details.
You likely can apply mapping expressions to remove the error with explicit mapping, but I'd recommend removing the denormalization. The trouble with the denormalization the references so that Author can have a collection of Comments is that there is no way to enforce that a Comment's Author reference matches the Author of the Comment's EventInstance. I.e. I can have an author ID 1, with EventInstance ID 1, and create a Comment with ID 101. Nothing stops me from changing that Comment's Author ID to 2. Now I have two sources of truth as to who the author is. Comment.Author (ID: 2) and Comment.EventInstance.Author. (ID: 1) Wherever possible, remove those duplicate reference paths.
EDIT: Mapping feedback
With mapping references and back, you will want to avoid double-mapping in the sense of mapping a relationship from both entities. Map relationships from one side. You could try:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EventInstance>()
.HasOne(x => x.Author)
.WithMany(x => x.Events)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<EventInstance>()
.HasMany(x => x.Comments)
.WithOne(x => x.Event)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName)
.HasMany(x => x.Comments)
.WithOne(x => x.Author)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
Edit #2: If Authors can create comments for another author's events, then I would suggest removing the Comments relationship from the Author side. Comments can be retrieved as a top-level entity or through the Events as a top level entity.
For instance:
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
public Guid EventId { get; set; }
public virtual EventInstance Event { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
}
Then in mapping:
modelBuilder.Entity<Comment>()
.HasOne(x => x.Author)
.WithMany()
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Comment>()
.HasOne(x => x.Event)
.WithMany(x => x.Comments)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName);
This switches around the mapping from the Event over to the Comment, but the Comment maintains a bi-directional reference to it's Event, but a single-direction reference to its Author. (HasMany()) This should satisfy EF's mapping. On a side note with the explicit relationship mapping using OnModelCreating or IEntityTypeConfiguration we don't need the [ForeignKey] attributes so I removed those.

Entity Framework Core: How to solve Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths

I'm using Entity Framework Core with Code First approach but recieve following error when updating the database:
Introducing FOREIGN KEY constraint 'FK_AnEventUsers_Users_UserId' on table 'AnEventUsers' 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.
My entities are these:
public class AnEvent
{
public int AnEventId { get; set; }
public DateTime Time { get; set; }
public Gender Gender { get; set; }
public int Duration { get; set; }
public Category Category { get; set; }
public int MinParticipants { get; set; }
public int MaxParticipants { get; set; }
public string Description { get; set; }
public Status EventStatus { get; set; }
public int MinAge { get; set; }
public int MaxAge { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
public int UserId { get; set; }
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public double Rating { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
}
public class AnEventUser
{
public int AnEventId { get; set; }
public AnEvent AnEvent { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>()
.HasOne(eu => eu.User)
.WithMany(e => e.AnEventUsers)
.HasForeignKey(eu => eu.UserId);
}
public DbSet<AnEvent> Events { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<AnEventUser> AnEventUsers { get; set; }
}
The issue I thought was that if we delete a User the reference to the AnEvent will be deleted and also the reference to AnEventUser will also be deleted, since there is a reference to AnEventUser from AnEvent as well we get cascading paths. But I remove the delete cascade from User to AnEventUser with:
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
But the error doesn't get resolved, does anyone see what is wrong? Thanks!
In your sample code in OnModelCreating you have declared modelBuilder.Entity<AnEventUser>().HasOne(e => e.User)... twice: at start of method and at end.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>() // THIS IS FIRST
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>() // THIS IS SECOND.
.HasOne(eu => eu.User) // THIS LINES
.WithMany(e => e.AnEventUsers) // SHOULD BE
.HasForeignKey(eu => eu.UserId); // REMOVED
}
Second call overrides first. Remove it.
This is what I did from the answer of Dmitry,
and It worked for me.
Class:
public class EnviornmentControls
{
public int Id { get; set; }
...
public virtual Environment Environment { get; set; }
}
And it's Mapping
public EnviornmentControlsMap(EntityTypeBuilder<EnviornmentControls> entity)
{
entity.HasKey(m => m.Id);
entity.HasOne(m => m.Environment)
.WithMany(m => m.EnviornmentControls)
.HasForeignKey(m => m.EnvironmentID)
.OnDelete(DeleteBehavior.Restrict); // added OnDelete to avoid sercular reference
}
These solutions didn't work for my case, but I found a way. I am not quite sure yet if it is safe but there's just something that's happening with deleting. So I modified the generated Migration File instead of putting an override.
onDelete: ReferentialAction.Cascade
The reason I did this because all the overriding mentioned above is not working for me so I manually removed the code which relates to Cascading of Delete.
Just check which specific relation being mentioned at the error so you can go straightly.
Hope this will be able to help for some people who's having the same issue as mine.
public Guid? UsuarioId { get; set; }
builder.Entity<Orcamentacao>()
.HasOne(x => x.Usuario)
.WithMany(x => x.Orcamentacaos)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false)
.HasForeignKey(x => x.UsuarioId);

Integrating SimpleMembership with Entity Framework

I'm trying to integrate the SimpleMembership tables with the rest of my Object Model - to manage all the entities from a single database and context.
Up to now the best recipe I've found for manually spinning up the SM tables (the entry point to combine SimpleMember with the rest of my Object Model) is found here. But, as cited in the comments section there are a couple mistakes in the code sample provided. The comments attempt to provide corrections but, due to formatted, really hard to follow.
I'm 80% the way there but getting stuck with the Foreign Key generation for the Membership table. Does the code within OnModelCreating block belong in the MyDbContext class? I'm getting a compile error on the .WithMany(u => u.Members) line.
Membership.cs
[Table("webpages_Membership")]
public class Membership
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
public DateTime? CreateDate { get; set; }
[StringLength(128)]
public string ConfirmationToken { get; set; }
public bool? IsConfirmed { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
[Required, StringLength(128)]
public string Password { get; set; }
public DateTime? PasswordChangedDate { get; set; }
[Required, StringLength(128)]
public string PasswordSalt { get; set; }
[StringLength(128)]
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
<strike>public virtual ICollection<Role> Roles { get; set; }</strike>
EDIT: Originally I added the line above to remove a compiler complaint in the extraneous code block below. Removing this attempt to create a FK to Roles will align the rest of this code so that these model classes create a Migration that generates tables for SM.
OAuthMembership.cs
[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
[Key, Column(Order = 0), StringLength(30)]
public string Provider { get; set; }
[Key, Column(Order = 1), StringLength(100)]
public string ProviderUserId { get; set; }
public int UserId { get; set; }
}
Role.cs
[Table("webpages_Roles")]
public class Role
{
[Key]
public int RoleId { get; set; }
[StringLength(256)]
public string RoleName { get; set; }
public virtual ICollection<UserProfile> UserProfiles { get; set; }
}
UserProfile.cs
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
MyDbContext.cs
public MyDbContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Membership> Membership { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<OAuthMembership> OAuthMembership { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProfile>()
.HasMany<Role>(r => r.Roles)
.WithMany(u => u.UserProfiles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
EDIT: The block below was included in one of the article's comments but seems not to be needed.
//modelBuilder.Entity<Membership>()
// .HasMany<Role>(r => r.Roles)
// .WithMany(u => u.Members)
// .Map(m =>
// {
// m.ToTable("webpages_UsersInRoles");
// m.MapLeftKey("UserId");
// m.MapRightKey("RoleId");
// });
}
}
I followed the instructions in the article, and I also took into account the the comments that suggested the article was wrong in a few ways.
I ended up with the following classes:
UserProfile.cs
[Table("UserProfile")]
public class UserProfile
{
[Key, ForeignKey("Membership")]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<WebSecurity.Role> Roles { get; set; }
public WebSecurity.Membership Membership { get; set; }
}
You should notice right away the "ForeignKey" attribute I use on the UserId column. Since the user is first created in the Membership table, my UserProfile table is the dependent table.
Membership.cs
[Table("webpages_Membership")]
public class Membership
{
//public Membership()
//{
// Roles = new List<Role>();
// OAuthMemberships = new List<OAuthMembership>();
//}
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
public DateTime? CreateDate { get; set; }
[StringLength(128)]
public string ConfirmationToken { get; set; }
public bool? IsConfirmed { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
[Required, StringLength(128)]
public string Password { get; set; }
public DateTime? PasswordChangedDate { get; set; }
[Required, StringLength(128)]
public string PasswordSalt { get; set; }
[StringLength(128)]
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public UserProfile UserProfile { get; set; }
}
Per Richard's comments in the article, I commented out the constructor. I also created a reference back to the UserProfile, but not to roles.
OAuthMembership.cs
[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
[Key, Column(Order = 0), StringLength(30)]
public string Provider { get; set; }
[Key, Column(Order = 1), StringLength(100)]
public string ProviderUserId { get; set; }
public int UserId { get; set; }
//[Column("UserId"), InverseProperty("OAuthMemberships")]
//public Membership User { get; set; }
}
My OAuthMembership class remained basically the same; I commented out only the User attribute, per Richard's comment in the article.
AccountModel.cs+UsersContext
Finally, the UserContext class, where I create the association for the UsersInRoles table.
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<InternetApplication.Models.WebSecurity.Role>()
.HasMany<InternetApplication.Models.UserProfile>(r => r.UserProfiles)
.WithMany(u => u.Roles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
}
public DbSet<WebSecurity.Membership> Membership { get; set; }
public DbSet<WebSecurity.OAuthMembership> OAuthMembership { get; set; }
public DbSet<WebSecurity.Role> Roles { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
}
In addition to adding the UsersInRoles mapping, I added DbSet entries for each table.
Now that everything has been created, I can use my Add-Migration and Update-Database commands and use the following code snippet that combines the Membership, UserProfile, and Roles tables:
using (var db = new UsersContext())
{
var memberships = db.Membership
.Include("UserProfile")
.Include("UserProfile.Roles")
.ToList();
foreach (var member in memberships)
{
member.IsConfirmed = true;
}
db.SaveChanges();
}
This was a long post, but I hope that helps.
I used the answer to this question to automatically generate the models from the existing 'webpage_' tables in my database. This ensures that the models are created in the exact same way that SimpleMembership creates them. This resulted in the following code:
Models:
public partial class webpages_Membership
{
public int UserId { get; set; }
public Nullable<System.DateTime> CreateDate { get; set; }
public string ConfirmationToken { get; set; }
public Nullable<bool> IsConfirmed { get; set; }
public Nullable<System.DateTime> LastPasswordFailureDate { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
public string Password { get; set; }
public Nullable<System.DateTime> PasswordChangedDate { get; set; }
public string PasswordSalt { get; set; }
public string PasswordVerificationToken { get; set; }
public Nullable<System.DateTime> PasswordVerificationTokenExpirationDate { get; set; }
}
public partial class webpages_Roles
{
public webpages_Roles()
{
this.webpages_UsersInRoles = new HashSet<webpages_UsersInRoles>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<webpages_UsersInRoles> webpages_UsersInRoles { get; set; }
}
public partial class webpages_UsersInRoles
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual webpages_Roles webpages_Roles { get; set; }
}
Fluent Mappings:
internal partial class MembershipMapping : EntityTypeConfiguration<webpages_Membership>
{
public MembershipMapping()
{
this.HasKey(t => t.UserId);
this.ToTable("webpages_Membership");
this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.Property(t => t.CreateDate).HasColumnName("CreateDate");
this.Property(t => t.ConfirmationToken).HasColumnName("ConfirmationToken").HasMaxLength(128);
this.Property(t => t.IsConfirmed).HasColumnName("IsConfirmed");
this.Property(t => t.LastPasswordFailureDate).HasColumnName("LastPasswordFailureDate");
this.Property(t => t.PasswordFailuresSinceLastSuccess).HasColumnName("PasswordFailuresSinceLastSuccess");
this.Property(t => t.Password).HasColumnName("Password").IsRequired().HasMaxLength(128);
this.Property(t => t.PasswordChangedDate).HasColumnName("PasswordChangedDate");
this.Property(t => t.PasswordSalt).HasColumnName("PasswordSalt").IsRequired().HasMaxLength(128);
this.Property(t => t.PasswordVerificationToken).HasColumnName("PasswordVerificationToken").HasMaxLength(128);
this.Property(t => t.PasswordVerificationTokenExpirationDate).HasColumnName("PasswordVerificationTokenExpirationDate");
}
}
internal partial class RolesMapping : EntityTypeConfiguration<webpages_Roles>
{
public RolesMapping()
{
this.HasKey(t => t.RoleId);
this.ToTable("webpages_Roles");
this.Property(t => t.RoleId).HasColumnName("RoleId");
this.Property(t => t.RoleName).HasColumnName("RoleName").IsRequired().HasMaxLength(256);
}
}
internal partial class UsersInRolesMapping : EntityTypeConfiguration<webpages_UsersInRoles>
{
public UsersInRolesMapping()
{
this.HasKey(t => new { t.UserId, t.RoleId });
this.ToTable("webpages_UsersInRoles");
this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.Property(t => t.RoleId).HasColumnName("RoleId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
this.HasRequired(t => t.webpages_Roles).WithMany(t => t.webpages_UsersInRoles).HasForeignKey(d => d.RoleId);
}
}
Database Context:
public class MembershipContext : DbContext, IDisposable
{
public DbSet<webpages_Membership> Membership { get; set; }
public DbSet<webpages_Roles> Roles { get; set; }
public DbSet<webpages_UsersInRoles> UsersInRoles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MembershipMapping());
modelBuilder.Configurations.Add(new RolesMapping());
modelBuilder.Configurations.Add(new UsersInRolesMapping());
base.OnModelCreating(modelBuilder);
}
}
Note that I have excluded the OAuthMembership table, because I didn't need it for my solution. But if you follow the steps in the link I provided above you can easily include that table as well.
Starting from a blank MVC4 Internet Template I ran the project so as to create the SimpleMembership tables in a fresh db - then used EF's Reverse Engineer tool to create POCOs from those tables. Stepped thru it line by line to find the error and edited the code block in the OP.
With that code in place I used Package Manager to 'Add-Migration' and 'Update-Database'. Initial tests confirm everything works - I suppose I'll have to revisit if I find edge-cases that expose any deeper problems.

Entity Framework 5 using multiple relationships between two POCOs

I'm having issues applying multiple relationships (or possibly foreignkey) on two POCO objects. I've got the first relationship many-to-many working and when the database is created it creates the three tables (Projects, Users and ProjectsUsers) needed for the relationship.
Code so far:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Name = new Name();
}
public int UserId { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public Name Name { get; set; }
public ICollection<Project> ManagedProjects { get; set; }
}
public class ProjectConfiguration : EntityTypeConfiguration<Project>
{
public ProjectConfiguration()
{
HasMany(x => x.Users)
.WithMany(x => x.ManagedProjects);
}
}
public UserConfiguration()
{
HasMany(x => x.ManagedProjects)
.WithMany(x => x.Users);
}
Now I want to add an optional one-to-one relationship of Project.ManagingUser -> User. However, I can't seem to figure out how to indicate this in the configuration.
Code for what I think is needed:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public int? ManagingUserId { get; set; }
public User ManagingUser { get; set; }
public ICollection<User> Users { get; set; }
}
I don't think the User object needs to change.
This shows my last attempt on mapping the new relationship:
public ProjectConfiguration()
{
HasMany(p => p.Users)
.WithMany(u => u.Projects);
this.HasOptional(p => p.ManagingUser)
.WithOptionalDependent()
.Map(m=>m.MapKey("ManagingUserId"))
.WillCascadeOnDelete(false);
}
What is happening when the database is created, I now end up with only two tables (Projects and Users). And it looks like it is only trying to setup the one-to-one relationship.
Can someone tell me what I'm missing?
Richard I've not changed the UserConfiguration and below is the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext() : base(Properties.Settings.Default.ConnectionString)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Project> Projects { get; set; }
}
You probably want WithMany instead of WithOptionalDependent - it's a one:many relationship, not a one:one.
HasOptional(p => p.ManagingUser)
.WithMany()
.HasForeignKey(m => m.ManagingUserId)
.WillCascadeOnDelete(false);
EDIT
I think you're missing the OnModelCreating override from the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ProjectConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}

Code First Circular Reference Foreign Key Configuration

The following code creates foreign key errors when all code is not commented.
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public int FavoriteChildId { get; set; }
public Child FavoriteChild { get; set; }
//public int WorstChildId { get; set; }
public Child WorstChild { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
//public int ParentId { get; set; }
public Parent Parent { get; set; }
}
public class CFContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
}
It works if the foreign key names aren't specified but then there's no way to edit the models. Does anyone know how to specify the foreign key names?
Following the naming convention will create the proper FK's above - your code:
public int WorstChildId { get; set; }
public Child WorstChild { get; set; }
Does create the FK of WorstChildId for WorstChild. However, when I tried the code you had above, I got a multiple delete path error (Parent -> WorstChild -> ChildTable, Parent -> FavoriteChild -> ChildTable)
You can set either one, or both of your mappings to not cascade on delete, and that will fix your problem:
public class CFContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(c => c.Parent)
.WithRequiredPrincipal(p => p.WorstChild)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Child>()
.HasRequired(c => c.Parent)
.WithRequiredPrincipal(p => p.FavoriteChild)
.WillCascadeOnDelete(false);
}
}