I've been wrestling with this for a while and can't quite figure out what's happening. I have a Card entity which contains Sides (usually 2) - and both Cards and Sides have a Stage. I'm using EF Codefirst migrations and the migrations are failing with this error:
Introducing FOREIGN KEY constraint 'FK_dbo.Sides_dbo.Cards_CardId' on
table 'Sides' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
Here's my Card entity:
public class Card
{
public Card()
{
Sides = new Collection<Side>();
Stage = Stage.ONE;
}
[Key]
[Required]
public virtual int CardId { get; set; }
[Required]
public virtual Stage Stage { get; set; }
[Required]
[ForeignKey("CardId")]
public virtual ICollection<Side> Sides { get; set; }
}
Here's my Side entity:
public class Side
{
public Side()
{
Stage = Stage.ONE;
}
[Key]
[Required]
public virtual int SideId { get; set; }
[Required]
public virtual Stage Stage { get; set; }
[Required]
public int CardId { get; set; }
[ForeignKey("CardId")]
public virtual Card Card { get; set; }
}
And here's my Stage entity:
public class Stage
{
// Zero
public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
// Ten seconds
public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");
public static IEnumerable<Stage> Values
{
get
{
yield return ONE;
yield return TWO;
}
}
public int StageId { get; set; }
private readonly TimeSpan span;
public string Title { get; set; }
Stage(TimeSpan span, string title)
{
this.span = span;
this.Title = title;
}
public TimeSpan Span { get { return span; } }
}
What's odd is that if I add the following to my Stage class:
public int? SideId { get; set; }
[ForeignKey("SideId")]
public virtual Side Side { get; set; }
The migration runs successfully. If I open up SSMS and look at the tables, I can see that Stage_StageId has been added to Cards (as expected/desired), however Sides contains no reference to Stage (not expected).
If I then add
[Required]
[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int StageId { get; set; }
To my Side class, I see StageId column added to my Side table.
This is working, but now throughout my application, any reference to Stage contains a SideId, which is in some cases totally irrelevant. I'd like to just give my Card and Side entities a Stage property based on the above Stage class without polluting the stage class with reference properties if possible... what am I doing wrong?
Because Stage is required, all one-to-many relationships where Stage is involved will have cascading delete enabled by default. It means, if you delete a Stage entity
the delete will cascade directly to Side
the delete will cascade directly to Card and because Card and Side have a required one-to-many relationship with cascading delete enabled by default again it will then cascade from Card to Side
So, you have two cascading delete paths from Stage to Side - which causes the exception.
You must either make the Stage optional in at least one of the entities (i.e. remove the [Required] attribute from the Stage properties) or disable cascading delete with Fluent API (not possible with data annotations):
modelBuilder.Entity<Card>()
.HasRequired(c => c.Stage)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Side>()
.HasRequired(s => s.Stage)
.WithMany()
.WillCascadeOnDelete(false);
I had a table that had a circular relationship with others and I was getting the same error. Turns out it is about the foreign key which was not nullable. If the key is not nullable the related object must be deleted, and circular relations don't allow that. So use nullable foreign key.
[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Anybody wondering how to do it in EF core:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
..... rest of the code.....
I was getting this error for lots of entities when I was migrating down from an EF7 model to an EF6 version. I didn't want to have to go through each entity one at a time, so I used:
builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
You can set cascadeDelete to false or true (in your migration Up() method). Depends upon your requirement.
AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
In .NET Core I changed the onDelete option to ReferencialAction.NoAction
constraints: table =>
{
table.PrimaryKey("PK_Schedule", x => x.Id);
table.ForeignKey(
name: "FK_Schedule_Teams_HomeId",
column: x => x.HomeId,
principalTable: "Teams",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_Schedule_Teams_VisitorId",
column: x => x.VisitorId,
principalTable: "Teams",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
I had this issue also, I solved it instantly with this answer from a similar thread
In my case, I didn't want to delete the dependent record on key deletion. If this is the case in your situation just simply change the Boolean value in the migration to false:
AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Chances are, if you are creating relationships which throw this compiler error but DO want to maintain cascade delete; you have an issue with your relationships.
I fixed this. When you add the migration, in the Up() method there will be a line like this:
.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)
If you just delete the cascadeDelete from the end it will work.
Just for documentation purpose, to someone that comes on the future, this thing can be solved as simple as this, and with this method, you could do a method that disabled one time, and you could access your method normally
Add this method to the context database class:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
In .NET Core I played with all upper answers - but without any success.
I made changes a lot in DB structure and every time added new migration attempting to update-database, but received the same error.
Then I started to remove-migration one by one until Package Manager Console threw me exception:
The migration '20170827183131_***' has already been applied to the database
After that, I added new migration (add-migration) and update-database successfully
So my suggestion would be: clear out all your temp migrations, until your current DB state.
public partial class recommended_books : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.RecommendedBook",
c => new
{
RecommendedBookID = c.Int(nullable: false, identity: true),
CourseID = c.Int(nullable: false),
DepartmentID = c.Int(nullable: false),
Title = c.String(),
Author = c.String(),
PublicationDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.RecommendedBookID)
.ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
.ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
.Index(t => t.CourseID)
.Index(t => t.DepartmentID);
}
public override void Down()
{
DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
DropTable("dbo.RecommendedBook");
}
}
When your migration fails you are given a couple of options:
'Introducing FOREIGN KEY constraint 'FK_dbo.RecommendedBook_dbo.Department_DepartmentID' on table 'RecommendedBook' 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.'
Here is an example of using the 'modify other FOREIGN KEY constraints' by setting 'cascadeDelete' to false in the migration file and then run 'update-database'.
Make your Foreign key attributes nullable. That will work.
This sounds weird and I don't know why, but in my case that was happening because my ConnectionString was using "." in "data source" attribute. Once I changed it to "localhost" it workded like a charm. No other change was needed.
The existing answers are great I just wanted to add that I ran into this error because of a different reason. I wanted to create an Initial EF migration on an existing DB but I didn't use the -IgnoreChanges flag and applied the Update-Database command on an empty Database (also on the existing fails).
Instead I had to run this command when the current db structure is the current one:
Add-Migration Initial -IgnoreChanges
There is likely a real problem in the db structure but save the world one step at a time...
In .NET 5 < and .NET Core 2.0 < you can use .OnDelete(DeleteBehavior.Restrict) in OnModelCreating like #Nexus23 answer but you do not need to disable cascade for every model.
Example with join entity type configuration many-to-many:
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId)
.OnDelete(DeleteBehavior.Restrict),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId)
.OnDelete(DeleteBehavior.Restrict),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Sources:
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0
This does require you to remove the many to many relationship yourself or you will receive the following error when you remove a parent entity:
The association between entity types '' and '' has been severed, but
the relationship is either marked as required or is implicitly
required because the foreign key is not nullable. If the
dependent/child entity should be deleted when a required relationship
is severed, configure the relationship to use cascade deletes.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to
see the key values
You can solve this by using DeleteBehavior.ClientCascade instead which will allow EF to perform cascade deletes on loaded entities.
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId)
.OnDelete(DeleteBehavior.Cascade),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId)
.OnDelete(DeleteBehavior.ClientCascade),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0
None of the aforementioned solutions worked for me. What I had to do was use a nullable int (int?) on the foreign key that was not required (or not a not null column key) and then delete some of my migrations.
Start by deleting the migrations, then try the nullable int.
Problem was both a modification and model design. No code change was necessary.
The simple way is to, Edit your migration file (cascadeDelete: true) into (cascadeDelete: false) then after assign the Update-Database command in your Package Manager Console.if it's problem with your last migration then all right. Otherwise check your earlier migration history, copy those things, paste into your last migration file, after that do it the same thing. it perfectly works for me.
You could add this in your DataContext.cs, this works for me...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
I ran into the same problem and stuck for a long. The following steps saved me.
Go through the constraints and change the onDelete ReferentialAction to NoAction from Cascade
constraints: table =>
{
table.PrimaryKey("PK_table1", x => x.Id);
table.ForeignKey(
name: "FK_table1_table2_table2Id",
column: x => x.table2Id,
principalTable: "table2",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
Related
I have the following model classes in EF Core 2.2
public class User
{
[Key]
public long Id { get; set; }
[ForeignKey("Post")]
public long? PostId { get; set; }
public virtual Post Post { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
I have checked the relations with SSMS and they are fine.
But when I use
dbContext.Posts.Include(p => p.User);
EF Core generates the following join statement
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[Id] = [p.Users].[PostId]
I'm including User from Post and expect it to be as below
FROM Posts [p]
LEFT JOIN Users [p.Users] ON [p].[UserId] = [p.Users].[Id]
What is wrong with models?
Assume that I want to save last PostId in User model.
Is there an attribute to tell ef core which property to use when joining models?
From the discussion on the other response it looks like you want a User to contain Posts, but then also have the User track a reference to the Latest post. EF can map this, however you will probably need to be a bit explicit about the relationships.
For instance:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
public virtual Post LatestPost { get; set; }
}
[Table("Posts")]
public class Post
{
[Key]
public int PostId { get; set; }
public string PostText { get; set; }
public DateTime PostedAt { get; set; }
public virtual User User { get; set; }
}
then a Configuration to ensure EF wires up the relationship between user and posts correctly:
// EF6
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasMany(x => x.Posts)
.WithRequired(x => x.User)
.Map(x=>x.MapKey("UserId"));
HasOptional(x => x.LatestPost)
.WithMany()
.Map(x=>x.MapKey("LatestPostId"));
}
}
// EFCore
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasMany(x => x.Posts)
.WithOne(x => x.User)
.HasForeignKey("UserId");
HasOne(x => x.LatestPost)
.WithMany()
.IsRequired(false)
.HasForeignKey("LatestPostId");
}
}
You can accomplish this in the OnModelCreating event with the modelBuilder reference as well. Note here I am not declaring FK properties in my entities. This too is an option, but I generally recommend not declaring FKs to avoid reference vs. FK update issues. I've named the LatestPost FK as LatestPostId just to reveal a bit more accurately what it is for. It could be mapped to a "PostId" if you so choose.
Now lets say I go to add a new post and I want to associate it to the user, and assign it as the LatestPost for that user:
using (var context = new SomethingDbContext())
{
var user = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var newPost = new Post { PostText = "Test", User = user };
user.Posts.Add(newPost);
user.LatestPost = newPost;
context.SaveChanges();
}
You can update the "latest" post reference by loading the user and setting the LatestPost reference to the desired post.
There is a risk with this structure however that you should consider. The issue is that there is no way to reliably enforce (at a data level) that the LatestPost reference in a User actually references a post associated to that user. For instance, if I have a latest post pointing to a particular post, then I delete that post reference from the user's Posts collection, that can result in FK constraint errors, or simply disassociate the post from the user, but the user latest post still points at that record. I can also assign another user's post to this user's latest post reference. I.e.
using (var context = new SomethingDbContext())
{
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 1);
var user1 = context.Users.Include(x => x.Posts).Include(x => x.LatestPost)
.Single(x => x.UserId == 2);
var newPost = new Post { PostText = "Test", User = user1 };
user1.Posts.Add(newPost);
user1.LatestPost = newPost;
user2.LatestPost = newPost;
context.SaveChanges();
}
And that would be perfectly fine. User 2's "LatestPostId" would be set to this new post, even though this post's UserId only refers to User1.
A better solution when dealing with something like a Latest post is to not denormalize the schema to accommodate it. Instead, use unmapped properties in the entity for the latest post, or better, rely on projection to retrieve this data when it's needed. In both cases you would remove the LatestPostId from the User table
Unmapped property:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
[NotMapped]
public Post LatestPost
{
get { return Posts.OrderByDescending(x => x.PostedAt).FirstOrDefault(); }
}
}
The caveat of the unmapped property approach is that you need to remember to eager-load Posts on the User if you want to access this property, otherwise you will trip a lazy load. You also cannot use this property in Linq expressions that get sent to EF (EF6) though they may work with EFCore, but risk performance issues if the expression gets translated to in-memory early. EF will not be able to translated LatestPost to SQL since there would be no key in the schema.
Projection:
[Table("Users")]
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
Then if you want to retrieve a user and it's latest post:
var userAndPost = context.Users.Where(x => x.UserId == userId)
.Select(x => new { User = x, LatestPost = x.Posts.OrderByDescending(PostedAt).FirstOrDefault()} ).Single();
Projection with Select can retrieve entities of interest, or better, simply return the fields from those entities into a flattened view model or DTO to send to UI or such. This results in more efficient queries against the database. Using Select to retrieve the details you don't need to worry about eager-loading via Include, and when done correctly, will avoid pitfalls with lazy loading.
The relationship between User and Post is wrong. This should be your model:
public class User
{
[Key]
public long Id { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public long UserId { get; set; }
public virtual User User { get; set; }
}
That is one-to-many relationship: one user can have many posts and a post can have just one user.
You had enough of answers on this question to how to create the model , I will just highlight what's wrong in your class
You have craete postid in user model as FK and therefore it will always consider it as relationship between user and post , you have to remove that and design it something similar to what is said by steve
In short; we need a cache, that cache has related entities. We also need to then store the expired items in a separate table for compliance and archival reasons.
Imagine the following four classes:
public abstract class Cache
{
public int Id { get; set; }
public Cache()
{
Entities = new HashSet<Entity>();
}
public ICollection<Entity> Entities { get; set; }
}
public class AvailableCache : Cache
{
}
public class ArchivedCache : Cache
{
}
public class Entity
{
public int Id { get; set; }
public int? CacheId { get; set; }
public AvailableCache { get; set; }
public ArchivedCache { get; set; }
}
Together with the following DbContext:
public partial class DbCacheContext : DbContext
{
public virtual DbSet<AvailableCache> { get; set; }
public virtual DbSet<ArchivedCache> { get; set; }
public virtual DbSet<Entity> Entities { get; set; }
protected override void OnModelCreatning(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ArchivedCache>(p =>
{
p.Property(p => p.Id)
.ValueGeneratedOnAdd();
p.HasMany(p => p.Entities).WithOne(p => p.ArchivedCache).HasForeignKey(p => p.CacheId);
}
modelBuilder.Entity<AvailableCache>(p =>
{
p.HasMany(p => p.Entites).WithOne(p => p.AvailableCache).HasForeignkey(p => p.CacheId);
}
}
A recurring job populates the cache, checks for expired items and archives them. First attempt a column was added to ArchivedCache
public class ArchivedCache : Cache
{
public int OriginalId { get; set; }
}
This was then used as to save the original ID to define the relationship toward Entity. Then I tried to remove that, adding the ValueGeneratedOnAdd() and give it the same ID as AvailableCache.
The issue is that when trying to insert an Entity the foreign key constraint won't allow it since it doesn't exist in that table.
Since AvailableCache and ArchivedCache basically same object I would like to keep the relationship simple toward Entity.
Currently as I see it I have three options, I don't like any of them:
Define both IDs on Entity (I don't like this option because I don't want to clutter Entity with two ID:s that in the end point toward the same object, just in different tables/times)
Skip the ArchivedCache relationship and let the DBA worry about it since he was the one that specifically requested this.
I could remove the abstract class, resulting in one table with a discriminator but the DBA insisted on two tables.
But before I do either I wanted to check if there's perhaps something in EF Core that would allow this.
I am building a NET Core MVC app that consumes an existing MS SQL database. Primary keys and foreign keys are already established and working correctly at the database level.
I followed the example in this article and used package manager console to reverse engineer the models and database context from the database. This seemed to work well. It resulted in all models being added to my app's Models folder including a robust database context class. The problem I'm having is that relational information about these entities isn't being populated at runtime. I'm getting nulls for related entities for which foreign keys are established both in the database and in the fluent API code generated by the scaffolding process.
I have two tables, Mode and Submode, that are related via foreign key.
Scaffolding generated these two classes for the above two tables:
public partial class Submode
{
public Submode()
{
Contact = new HashSet<Contact>();
}
public int Id { get; set; }
public int ModeId { get; set; }
public string Code { get; set; }
public bool Visible { get; set; }
public bool IsDefault { get; set; }
public Mode Mode { get; set; }
public ICollection<Contact> Contact { get; set; }
}
public partial class Mode
{
public Mode()
{
Contact = new HashSet<Contact>();
Submode = new HashSet<Submode>();
}
public int Id { get; set; }
public string Code { get; set; }
public bool Visible { get; set; }
public bool IsDefault { get; set; }
public ICollection<Contact> Contact { get; set; }
public ICollection<Submode> Submode { get; set; }
}
Scaffolding also generated this fluent API snippet in the database context:
modelBuilder.Entity<Submode>(entity =>
{
entity.HasIndex(e => e.Code)
.HasName("UQ__Submode__A25C5AA75D2A9AE7")
.IsUnique();
entity.Property(e => e.Code)
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false);
entity.HasOne(d => d.Mode)
.WithMany(p => p.Submode)
.HasForeignKey(d => d.ModeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Submode_ModeId");
});
Every example I've read on setting foreign keys with fluent API show a similar pattern to the above snippets. But Mode comes back null for Submode.
Null at runtime
And I get a null reference exception in the returned view because I'm trying to display properties of the related Mode object. Am I missing some configuration or is there a problem with the scaffolded code?
UDPATE - as requested, here's the implementation that's fetching data from the database context.
public class SQLSubModeData : ISubModeData
{
private w4lkrContext _context;
public SQLSubModeData(w4lkrContext context)
{
_context = context;
}
public IQueryable<Submode> Get()
{
return _context.Submode.OrderBy(p => p.Id);
}
public Submode Get(int id)
{
return _context.Submode.FirstOrDefault(p => p.Id == id);
}
}
UPDATE (SOLVED) - Enabling lazy loading fixed the problem. Three steps got me there:
Installed Microsoft.EntityFrameworkCore.Proxies(2.1.2) via NuGet
Updated Startup.cs -> AddDbContext() method, as follows:
services.AddDbContext(options => options.UseLazyLoadingProxies().UseSqlServer(_configuration.GetConnectionString("W4LKR")));
Made all navigation properties virtual. This had to be done on every model in the app, not just the one being called in my example above. Errors are thrown if even one is left out.
But Mode comes back null for Submode.
Since your Navigation Properties aren't declared as virtual, you have disabled Lazy Loading, so EF will only populate your Navigation Properties if you do Eager Loading, or Explicit Loading.
See Loading Related Data
I've got an Entity as follows:
public class EntityX {
public int Id { get; set; }
...
[ForeignKey("Scheduled By")]
public string ScheduledById { get; set; }
public virtual ApplicationUser ScheduledBy { get; set; }
}
When I try to insert a value into the table, I get the following error:
"The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.EntityX_dbo.ApplicationUsers_ScheduledById". The conflict
occurred in database "DB", table "dbo.ApplicationUsers", column 'Id'.
The statement has been terminated."
The first thing that comes to mind is that the ApplicationUser table is empty because the IdentityUser table (AspNetUsers) holds all the values. However, its TPH and has a Discriminator column populated with the ApplicationUser table name.
I've verified that the correct Id is being populated when sent in the DB (i.e. it corresponds to an actual User ID) but can't figure out why this is happening.
Thank you in advance. Cheers!
UPDATE:
The space in "Scheduled By" was a typo. It was copied over incorrectly. The actual code has it written as pointed out "ScheduledBy".
UPDATE 2:
The problem it seems lies in the contexts somewhere. I've got two, one DataContext that extends from DbContext as follows:
public class DataContext : DbContext
{
public DbSet<EntityX> EntityXs { get; set; }
...
}
static DataContext()
{
Database.SetInitializer<DataContext> (new CreateInitializer ());
}
public DataContext()
: base("DataContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
...
}
And another, extending from IdentityDbContext as follows:
public class SecurityContext : IdentityDbContext<ApplicationUser>
{
static SecurityContext()
{
Database.SetInitializer<SecurityContext> (new CreateInitializer ());
}
public SecurityContext()
: base("SecurityContext")
{
Database.Initialize(force: true);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
modelBuilder.Entity<ApplicationUser>()
.ToTable("AspNetUsers");
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
base.OnModelCreating(modelBuilder);
}
With this, the configuration works... but I'm presented with an issue where I've got an extra IdentityRole_Id appearing in the AspNetUserRoles table as described at this post: EF Code First Migration unwanted column IdentityRole_Id. To work around that issue, I followed Hao Kung's advice here: Create ASP.NET Identity tables using SQL script and changed my contexts' OnModelCreating methods this way:
DataContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}
And SecurityContext...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var user = modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName).IsRequired();
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers"); //Needed?
modelBuilder.Entity<IdentityUserRole>()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>()
.HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey })
.ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name).IsRequired();
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}
Although doing this builds the DB correctly, with a Discriminator column in the AspNetUsers table populating with "ApplicationUser" as the value and without the extra columns in AspNetUserRoles, any attempt at inserting the user's Id value into EntityX as FK fails.
I'm completely lost.
So, it turns out that there wasn't any problem with how the entity was set up. It was due to some issue that was arising when migrating to the same DB via the two contexts. Merging them into one fixed the issue. I've posted how I resolved things below. Hopefully this saves someone else time and torment.
public class SecurityContextContext : IdentityDbContext<ApplicationUser>
{
public DbSet<EntityX> EntityX { get; set; }
...
static SecurityContext()
{
Database.SetInitializer<SecurityContext> (new CreateInitializer());
}
public SecurityContext()
: base("SecurityContext")
{
Database.Initialize(force: true);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var user = modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName).IsRequired();
user.HasKey(u => u.Id);
var appUser = modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers"); //Needed?
appUser.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
appUser.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
appUser.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
appUser.Property(u => u.UserName).IsRequired();
modelBuilder.Entity<IdentityUserRole>()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>()
.HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey })
.ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name).IsRequired();
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}
This configuration works for me. Although the ApplicationUser table does not generate via this mapping, there is still a Discriminator column created in the AspNetUsers table filled with "ApplicationUser". The AspNetUsers table also gets the extra fields that I defined in the ApplicationUser class. The IdentityRole_Id is eliminated and I'm able to assign roles and get them successfully. The FK issue is also resolved. Everything works as intended.
As the error message indicates, your foreign key needs to be associated with a valid entity property. If you place the ForeignKey attribute on a foreign key property the string parameter represents the name of the associated navigation property. Remove the whitespace to match the name of the navegation property:
public class EntityX
{
public int Id { get; set; }
...
[ForeignKey("ScheduledBy")]
public string ScheduledById { get; set; }
public virtual ApplicationUser ScheduledBy { get; set; }
}
use this:
public class EntityX
{
public int Id { get; set; }
[ForeignKey("ScheduledBy")]
public string ScheduledById { get; set; }
[ForeignKey("ScheduledById")]
[InverseProperty("EntityX_1")]
public virtual ApplicationUser ScheduledBy{ get; set; }
}
public class ApplicationUser
{
public string ScheduledById { get; set; }
.
.
.
[InverseProperty("ScheduledBy")]
public virtual ICollection<EntityX> EntityX_1{ get; set; }
}
I have two tables. Questions and UserProfile
public QuestionMap()
{
this.HasKey(t => t.QuestionId);
this.Property(t => t.Title)
.IsRequired()
.HasMaxLength(100);
this.Property(t => t.CreatedBy)
.IsRequired();
}
public UserProfileMap()
{
this.HasKey(t => t.UserId);
this.Property(t => t.UserName)
.IsRequired()
.HasMaxLength(56);
}
My repository call looks like this at the moment:
public virtual IQueryable<T> GetAll()
{
return DbSet;
}
Is there a way that I can change the repository call so that it will go to the database, join the Question and UserProfile tables and then bring back a list that has the UserName from the UserProfile table? I realize I may have to create another class (that includes UserName) for the return type and I can do that.
You can't configure property mapping to some field of joined entity. But you can create navigation properties in your Question and UserProfile entites, which will provide joined enitites:
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
public virtual UserProfile CreatedBy { get; set; }
}
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
And add mapping configuration to user mapping:
HasMany(u => u.Questions)
.WithRequired(q => q.CreatedBy)
.Map(m => m.MapKey("UserId"))
.WillCascadeOnDelete(true);
Now you will be able to eager load users when querying questions:
var questions = context.Questions.Include(q => q.CreatedBy);
foreach(var question in questions)
Console.WriteLine(question.CreatedBy.UserName);
You should make the join in your controller not your repository. The repository is supposed to deal with only one entity in your database.
Or you can create a View in your database, and add it as an entity to your entity framework model.
Actually, you CAN join tables using LINQ without navigation properties. Let me demonstrate:
public class Question
{
public int QuestionID { get; set; }
public string Title { get; set; }
public string TextBody { get; set; }
public int CreatedBy { get; set; }
}
public class UserProfile
{
[Key]
public int UserID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
So that's the (very simple) model. Now, I'm not at all familiar with the IQueryable<T> GetAll() method shown in the OP's post, but I'm assuming that this method can be used to return an IQueryable<Question> that would, when executed, get all the Questions from the database. In my example, I'm just going to use MyDbContext instead, but this could be replaced with your other method if you prefer it...
So the LINQ join would be done as follows:
public void SomeMethod()
{
var results = MyDbContext.Questions.Join(MyDbContext.UserProfiles,
q => q.CreatedBy,
u => u.UserID,
(q,u) => new {
q.QuestionID,
q.Title,
q.TextBody,
u.UserName
});
foreach (var result in results)
{
Console.WriteLine("User {0} asked a question titled {1}:",
result.UserName, result.Title);
Console.WriteLine("\t{0}", result.TextBody)
}
}
Now, I do think that navigation properties are MUCH better for such obvious and common relationships. However, there are times when you might not want to add a navigation property for a relationship that is seldom referenced. In those cases, it might just be better to use the Join method.