I have entity Program that has two foreign keys to my subject table (MainContactSubjectId, SecondaryContactSubjectId). Both main and secondary are nullable longs. For some reason, when I try to insert entity Program it errors (Internal Server Error) and will not let me insert unless Main and Secondary are present. Below is my entity Program and some of my dbContext. Can anyone see what I am doing wrong?
[Table("Program")]
public class Program : Entity<long>
{
[Required]
public int TenantId { get; set; }
[Required]
public long ProgramTypeId { get; set; }
[Required]
[MaxLength(4000)]
public string ProgramName { get; set; }
public long? MainContactSubjectId { get; set; }
public long? SecondaryContactSubjectId { get; set; }
public virtual ICollection<AppTables.Case_ProgramRequirements.Case_ProgramRequirement> Case_ProgramRequirement { get; set; }
public virtual AppTables.ProgramTypes.ProgramType ProgramType { get; set; }
public virtual AppTables.Subjects.Subject MainSubject { get; set; }
public virtual AppTables.Subjects.Subject SecondarySubject { get; set; }
}
I'm guessing the problem is here but I'm not sure what it is. My best
guess is the .HasRequired but I'm not sure how to rewrite it.
Without this code, the foreign keys are not getting created correctly
and circular reference issues. The WillCascadeOnDelete(false) stops the
circular reference issue.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
Edit 1:
I am fairly confident my problem is in the data access layer but if not, I am making an ajax call directly to my Application Service layer "Create". As I step through it everything looks perfect. It hits the return and then I get Internal Server Error. No other details. I tried wrapping in try/catch and catch is never hit.
I am using Asp.net Boilerplate framework. I have posted in their private forum too with no luck, yet. I just assumed it was a data access layer issue which is why I posted here.
public async Task<> Create(ProgramInput model)
{
Program domainModel = new Program();
domainModel.TenantId = (int)AbpSession.TenantId;
domainModel.ProgramName = model.ProgramName;
domainModel.ProgramTypeId = model.ProgramTypeId;
domainModel.MainContactSubjectId = model.MainContactId;
domainModel.SecondaryContactSubjectId = model.SecondaryContactId;
domainModel.CreatedBy = (long)AbpSession.UserId.Value;
domainModel.CreatedDate = Clock.Now;
domainModel.IsDeleted = false;
await _programRepository.InsertAsync(domainModel);
return;
}
Edit 2:
Here is my Subject Table. It has 50 some columns and 10 foreign keys but I will shorten with just the pertinent data.
public class Subject : Entity<long>
{
public Subject()
{
this.ProgramsMain = new HashSet<AppTables.Programs.Program>();
this.ProgramsSecondary = new HashSet<AppTables.Programs.Program>();
}
[Required]
public int TenantId { get; set; }
[Required]
public long SubjectTypeId { get; set; }
[MaxLength(1000)]
public string FirstName { get; set; }
[MaxLength(1000)]
public string MiddleName { get; set; }
[MaxLength(1000)]
public string LastName { get; set; }
[MaxLength(100)]
public string Suffix { get; set; }
[MaxLength(3000)]
public string FullName { get; set; }
//A TON MORE COLUMNS ....
public virtual AppTables.SubjectTypes.SubjectType SubjectType { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsMain { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsSecondary { get; set; }
ANSWER
I figured out my answer. I switched .HasRequired to .HasOptional and everything now works. Sorry to have wasted everyone's time. Thanks!
From the OP's answer in his question edit and comment:
Change .HasRequired to .HasOptional as per the code below.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
Related
I have the following entities
public class Course
{
public long Id { get; set; }
public virtual ICollection<User> Users{ get; set; }
public virtual ICollection<UserCourse> CourseUsers { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
public bool IsRequired { get; set; }
}
with the following mappings for
UserCourse mapping :
builder
.HasOne(nav => nav.User)
.WithMany(self => self.UserCourses)
.HasForeignKey(fk => fk.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(nav => nav.Course)
.WithMany(self => self.CourseUsers)
.HasForeignKey(fk => fk.CourseId)
.OnDelete(DeleteBehavior.Cascade);
and the User mapping
builder
.HasMany(nav => nav.Courses)
.WithMany(nav => nav.Users);
When trying to create a new migration I'm not exactly sure why I'm getting this.
Cannot use table 'UserCourse' for entity type 'UserCourse' since it is
being used for entity type 'UserCourse(Dictionary<string, object>)'
and potentially other entity types, but there is no linking
relationship. Add a foreign key to 'UserCourse' on the primary key
properties and pointing to the primary key on another entity typed
mapped to 'UserCourse'.
I understand what the error is, but not sure how to force the UserCourse mapping to use the User mapping generated join table or vice-versa
Also, I need the direcat mapping for OData, and the indirect mapping using the join entity to conduct operations on DbSet<UserCourse>
The public virtual ICollection<User> Users{ get; set; } in Course entity and the the public virtual ICollection<Course> Courses { get; set; } in Users entity are redundant. The entities should look more like this
public class Course
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
}
And the OnModelCreating method should have this code
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserCourse>()
.HasKey(uc => new { uc.UserId, uc.CourseId });
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.Course)
.WithMany(c => c.Users)
.HasForeignKey(uc => uc.CourseId);
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.User)
.WithMany(c => c.Courses)
.HasForeignKey(uc => uc.UserId);
}
If you use EF core 5 you can directly skip the join table. It will be generated and handled by EF behind the scenes. More on the topic here https://www.thereformedprogrammer.net/updating-many-to-many-relationships-in-ef-core-5-and-above/
I know there are some answered questions on SO about 1:0..1-relationships. I have looked at this and this, but don't think they apply to my question.
I have these three (simplified) models in a CMS-system.
public class FrontPageItem
{
public int Id { get; set; }
public int ItemType { get; set; } // 1 = Article, 2 = WebPage, etc...
public int? ArticleId { get; set; }
public Article Article { get; set; }
public int? WebPageId { get; set; }
public WebPage WebPage { get; set; }
}
public class Article
{
public int Id { get; set; }
public string Title { get; set; }
public string Preamble { get; set; }
public string MainText { get; set; }
public int? FrontPageItemId { get; set; }
public FrontPageItem FrontPageItem { get; set; }
}
public class WebPage
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? FrontPageItemId { get; set; }
public FrontPageItem FrontPageItem { get; set; }
}
The relationships between a FrontPageItem and each of the different element types are one-to-zero-or-one. An element can exist without being added as a FrontPageItem, meaning that a FrontPageItem has a relationship to just one element, either an Article or a WebPage.
In an attempt to configure the relationships, I have added this bit of code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasOne(p => p.FrontPageItem)
.WithOne(i => i.Article)
.HasForeignKey<FrontPageItem>(b => b.Id);
modelBuilder.Entity<WebPage>()
.HasOne(p => p.FrontPageItem)
.WithOne(i => i.WebPage)
.HasForeignKey<FrontPageItem>(b => b.Id);
}
But I don't think it's correct. I haven't made the CRUD-views for FrontPageItem yet, but if I try to add items directly in the SQL Server Object Explorer in VS, I have to enter a value for the PK.
What am I doing wrong?
As you said:
The relationships between a FrontPageItem and each of the different element types are one-to-zero-or-one. An element can exist without being added as a FrontPageItem, meaning that a FrontPageItem has a relationship to just one element, either an Article or a WebPage.
Then remove ArticleId and WebPageId from FrontPageItem as EF core support one-to-one-or-zero association without foreign key in principle table:
public class FrontPageItem
{
public int Id { get; set; }
public int ItemType { get; set; } // 1 = Article, 2 = WebPage, etc...
public Article Article { get; set; }
public WebPage WebPage { get; set; }
}
Then the Fleunt API configuration for Article and WebPage as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasOne(a => a.FrontPageItem)
.WithOne(f => f.Article)
.HasForeignKey<Article>(a => a.FrontPageItemId); // <-- Here it is
modelBuilder.Entity<WebPage>()
.HasOne(wp => wp.FrontPageItem)
.WithOne(f => f.WebPage)
.HasForeignKey<WebPage>(wp => wp.FrontPageItemId); // <--Here it is
}
I have the following code.
Why are my navigation properties (Requirement in Course, and Courses in Requirement) are null?
public class Course : AbsEntity {
[Key]
public string Title { get; set; }
public string Term { get; set; }
public int Year { get; set; }
public string CourseId { get; set; }
public double GradePercent { get; set; }
public string GradeLetter { get; set; }
public string Status { get; set; }
public int ReqId { get; set; }
public Requirement Requirement { get; set; }
}
public class Requirement : AbsEntity {
[Key]
public int ReqId { get; set; }
public string ReqName { get; set; }
public ICollection<Course> Courses { get; set; }
}
// In DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().HasOne(c => c.Requirement).WithMany(r => r.Courses).HasForeignKey(c => c.ReqId);
modelBuilder.Entity<Requirement>().HasMany(r => r.Courses).WithOne(c => c.Requirement);
}
First thing is you don't need to configure your relationship twice, you just need to do it one time:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().HasOne(c => c.Requirement)
.WithMany(r => r.Courses)
.HasForeignKey(c => c.ReqId);
}
Second thing is if you are doing a query and you are expecting to lazy load the related properties, I'm afraid is not going to be possible. EF 7 doesn't support lazy loading yet. As you can see there is a backlog item tracking Lazy Loading. So, if you need to load a related entity, you should use explicit loading using Include method:
var query= ctx.Courses.Include(c=>c.Requirement).Where(...)...;
Here is the simplified version of my problem. Think of a survey application:
There are questions,
A question have many options, an option can only belong to a question
A question should have one category, a category can belong to many questions
A question may have many tags, a tag can belong to many questions
Besides, I would like to define dependencies between a questions and options of other questions such that if a user has voted 'No' option for 'Do you have a car?' question, he/she will not be asked for 'What is the brand of your car?'.
Both auto generated database schema created by Code First and auto generated code created by database first are not satisfying. Below are codes and db schemas generated for both methods.
EF can handle Question - Tag relationship as expected but it cannot handle Dependency relationship between Question and Option (as I understood) because Question already has its own options.
In terms of EF, what would be the ideal code/db design for this case?
Code First - Code
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<Option> DependencyOptions { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
public long CategoryId { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Question> DependencyQuestions { get; set; }
[ForeignKey("QuestionId")]
public virtual Question Question { get; set; }
public long QuestionId { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
Code First - Tables
Db First - Tables
Db First - Code
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public long CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<Option> Options1 { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
public long QuestionId { get; set; }
public virtual Question Question { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
My understanding is you want a subsequent question to be dependent on the answer to a prior question...
My suggestion would be to to have a relationship table between questions and options called something like RequiredAnswer. This relationship will signify that a user must have answered a question or questions with one or all (up to you for implementation) of the related options (your diagrams don't include an answers table). Then it's up to the code implementation on how you want to use this relationship.
I'm not sure if I understood it right but I think you're going to need (and already have) a many-to-many on Option/Question - plus, you also have a one-to-many (owner) relation in between them.
I don't know enough to discuss whether that is the best solution - but taking your word for having both the 'dependency' relationship (and the other one)...
...for that you'd need to 'fine tune' your relationships and your index tables. EF/CF creates the default ones in the background, what you need I think is to create the relations in fluent configuration - and add extra columns yourself.
And I would generally recommend doing your own configuration in the
code - vs attributes / default one - as in any complex scenarios that
gives you more options and control over it, less errors etc. In my
case, I just like to know which tables and columns are created for me.
And here is a specific sample code (I removed other tables unnecessary, just Q/O)...
public class QuestionContext : DbContext
{
public DbSet<Question> Questions { get; set; }
public DbSet<Option> Options { get; set; }
public DbSet<QuestionOption> QuestionOptions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<QuestionOption>()
.HasKey(i => new { i.OptionID, i.QuestionID });
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Opiton)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.OptionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Question)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.QuestionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Option>()
.HasRequired(i => i.Question)
.WithMany(u => u.Options)
.HasForeignKey(i => i.QuestionId)
.WillCascadeOnDelete(false);
}
}
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<QuestionOption> DependencyOptions { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
// [ForeignKey("QuestionId")]
public virtual Question Question { get; set; }
public long QuestionId { get; set; }
public virtual ICollection<QuestionOption> DependencyOptions { get; set; }
}
public class QuestionOption
{
public long OptionID { get; set; }
public Option Opiton { get; set; }
public long QuestionID { get; set; }
public Question Question { get; set; }
public int DependencyType { get; set; }
public string DependencyNote { get; set; }
public bool Active { get; set; }
public bool UseEtc { get; set; }
}
With the following migration...
CreateTable(
"dbo.Questions",
c => new
{
Id = c.Long(nullable: false, identity: true),
Text = c.String(maxLength: 4000),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Options",
c => new
{
Id = c.Long(nullable: false, identity: true),
Text = c.String(maxLength: 4000),
QuestionId = c.Long(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Questions", t => t.QuestionId)
.Index(t => t.QuestionId);
CreateTable(
"dbo.QuestionOptions",
c => new
{
OptionID = c.Long(nullable: false),
QuestionID = c.Long(nullable: false),
DependencyType = c.Int(nullable: false),
DependencyNote = c.String(maxLength: 4000),
Active = c.Boolean(nullable: false),
UseEtc = c.Boolean(nullable: false),
})
.PrimaryKey(t => new { t.OptionID, t.QuestionID })
.ForeignKey("dbo.Options", t => t.OptionID)
.ForeignKey("dbo.Questions", t => t.QuestionID)
.Index(t => t.OptionID)
.Index(t => t.QuestionID);
You have two separate relationships defined (one one-to-many and other m-to-m), side by side.
Within QuestionOption table you can now manually specify all you need to add for your dependency (that's your DependencyOption - I just thought this naming clarifies it more). So you'd have something like Question(A) -> allows Option(B) - but given your logic you may need to add some more.
It looks like you'd need to establish relationships in between Question, Question, Option - so 3 indexes etc. Given the code above you can do that with just a simple extension if needed.
modelBuilder.Entity<QuestionOption>()
.HasKey(i => new { i.OptionID, i.QuestionLeftID, i.QuestionRightID });
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Opiton)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.OptionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.QuestionLeft)
.WithMany(u => u.DependencyOptionsLeft)
.HasForeignKey(i => i.QuestionLeftID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.QuestionRight)
.WithMany(u => u.DependencyOptionsRight)
.HasForeignKey(i => i.QuestionRightID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Option>()
.HasRequired(i => i.Question)
.WithMany(u => u.Options)
.HasForeignKey(i => i.QuestionId)
.WillCascadeOnDelete(false);
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<QuestionOption> DependencyOptionsLeft { get; set; }
public virtual ICollection<QuestionOption> DependencyOptionsRight { get; set; }
}
public class QuestionOption
{
public long QuestionLeftID { get; set; }
public Question QuestionLeft { get; set; }
public long QuestionRightID { get; set; }
public Question QuestionRight { get; set; }
public long OptionID { get; set; }
public Option Opiton { get; set; }
public int DependencyType { get; set; }
public string DependencyNote { get; set; }
public bool Active { get; set; }
public bool AllowForbid { get; set; }
}
For more complex scenarios (with many-to-many and manually defining relations in fluent code - which I recommend) - take a look at these detailed examples I made a while ago - it has most of the mappings you may need.
Many to many (join table) relationship with the same entity with codefirst or fluent API?
Code First Fluent API and Navigation Properties in a Join Table
EF code-first many-to-many with additional data
...that's if you need any fast pointers - let me know with questions - and some more details
Is there any way to Get a cascade on delete to happen when I remove a computer? Basically when I delete a computer I want it to remove the instance and all its references except Environments and Product.
Computer Entity:
public class Computer
{
[Key]
public int Id { get; set; }
public string IpAddress { get; set; }
public string Name { get; set; }
public string UserFriendlyName { get; set; }
public string Description { get; set; }
}
Instance Entity:
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
You need to define the relationships using the Fluent API. Use something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Computer>()
.HasRequired(x => x.Instance)
.WithRequiredPrincipal(x => x.Computer)
.WillCascadeOnDelete();
modelBuilder.Entity<Instance>()
.HasRequired(x => x.LastKnownProfile)
.WithRequiredPrincipal(x => x.Instance)
.WillCascadeOnDelete();
modelBuilder.Entity<Instance>()
.HasMany(x => x.TestResults)
.WithOptional(x => x.Instance)
.WillCascadeOnDelete();
}
This is documented pretty well on MSDN: Configuring Relationships with the Fluent API
check many to many relationship.is turning off cascade delete for State and deleting the related records manually