EF5 Code First Cascade on delete - c#

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

Related

Direct and Indirec many-to-many configuration using EF Core 5 using

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/

Exception in Entity Framework SaveChanges?

I'm trying to make an insert in a SQL database using Entity Framework 6 and I'm stuck on this issue that I cannot solve.
The error that I keep getting is :
UpdateException: Entities in 'Connect.CompanyFinancialDetails' participate in the 'Company_CompanyFinancialDetails' relationship. 0 related 'Company_CompanyFinancialDetails_Source' were found. 1 'Company_CompanyFinancialDetails_Source' is expected
I have these 2 entities:
public class Company
{
public long CUI { get; set; }
public string UserName { get; set; }
public string CompanyName { get; set; }
public string Symbol { get; set; }
public int? SharesCount { get; set; }
public decimal? SharePrice { get; set; }
public virtual Account Account { get; set; }
public virtual CompanyFinancialDetails CompanyFinancialDetails { get; set; }
}
public class CompanyFinancialDetails
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
// other properties
public decimal? NumberOfEmployees { get; set; }
public virtual Company Company { get; set; }
}
This is the Fluent API configuration:
public DbSet<Account> SignUpModels { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyFinancialDetails> CompanyFinancialDetails { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>()
.HasKey(k => k.Id)
.HasOptional(s => s.Company)
.WithRequired(d => d.Account);
modelBuilder.Entity<Company>()
.HasKey(k => k.CUI)
.HasOptional(s => s.CompanyFinancialDetails)
.WithRequired(d => d.Company);
}
The relationship that I want to have is 1-many (one Company has many CompanyFinancialDetails).
This is the code where I add the objects to the database:
Company co = Context.Find(username);
foreach (CompanyFinancialDetails s in c)
{
s.Company = co;
}
a.CompanyFinancialDetails.AddRange(c);
a.SaveChanges();
I get a list of CompanyFinancialDetails and I add them using the AddRange method. I had this issue before and what I did was to add the virtual property object to the object that I wanted to insert in the database and it worked. This is what I tried to do here: the Find() method gets the company object that is related to the CompanyFinancialDetails and for each CompanyFinancialDetails object an Company virtual property is adding the related company object.
Well, it didn't work, when the SaveChanges() method is called, I get that error. Any help would be appreciated.

How to configure this one-to-zero-or-one relationship?

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
}

One to Many with One Main in Code-First Entity Framework Core

One company can have many addresses, however each company has a main address.
I am looking to find the best way to create this kind of relation in EF Core.
Below is what I came up with. Is there a better way? Am I way off entirely?
Models
public class Company
{
public int Id { get; set; }
public int MainAddressId { get; set; }
public Address MainAddress { get; set; }
public ICollection<CompanyAddress> CompanyAddresses { get; set; }
// other company info
}
public class Address
{
public int Id { get; set; }
public int CompanyAddressId { get; set; }
public CompanyAddress CompanyAddress { get; set; }
// other address info
}
public class CompanyAddress
{
public int CompanyId { get; set; }
public Company Company { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
public bool IsMain { get; set; }
}
DataContext.cs
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<Company> Companies { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<CompanyAddress> CompanyAddresses { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CompanyAddress>()
.HasKey(ca => new {ca.CompanyId, ca.AddressId});
builder.Entity<CompanyAddress>()
.HasOne(ca => ca.Company)
.WithMany(ca => ca.CompanyAddresses)
.HasForeignKey(ca => ca.CompanyId)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<CompanyAddress>()
.HasOne(ca => ca.Address)
.WithOne(ca => ca.CompanyAddresses)
.HasForeignKey(ca => ca.AddressId)
.OnDelete(DeleteBehavior.Cascade);
}
}
In my opinion, dead on. There are always other ways. But this is straight-forward and easily understood. MainAddress and MainAddressId are redundant. You don't have lazy loading (virtual) so you can easily determine the main address by
dbContext.Companies.FirstOrDefault(p => p.Id = <myCompanyId>);
dbContext.CompanyAddresses.FirstOrDefault(p => p.CompanyId == <myCompanyId> && p.IsMain);
If you go with lazy loading later, just add .Include("Address") to the second query. And yes, you can combine the two.

Code First - Non Required Field Is Required

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);
}

Categories

Resources