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

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
}

Related

C# EF Core: How to map One-to-Zero with multiple properties (with Attributes only) [With Workaround]?

Community,
I tried my first Entity Framework Core project. Now I am unable to map the properties in my models, these are NOT One-to-One relations, I would call it "One-to-Zero":
public class ContactDetails
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("CreatedById")]
public User CreatedBy { get; set; }
[ForeignKey("UpdatedById")]
public User UpdatedBy { get; set; }
[ForeignKey("DeletedById")]
public User DeletedBy { get; set; }
// foreign keys
public int? CreatedById { get; set; }
public int? UpdatedById { get; set; }
public int? DeletedById { get; set; }
}
public class User
{
public int Id { get; set; }
public string MailAdress { get; set; }
public string Password { get; set; }
[ForeignKey("ContactDetailsByUserId")]
public ContactDetails ContactDetailsByUser { get; set; }
[ForeignKey("ContactDetailsByAdminId")]
public ContactDetails ContactDetailsByAdmin { get; set; }
// foreign keys
public int? ContactDetailsByUserId { get; set; }
public int? ContactDetailsByAdminId { get; set; }
}
So ContactDetails can have three different Users.
Usercan have two different ContactDetails.
If I try to create a migration for this, I get this error:
Unable to determine the relationship represented by navigation property 'ContactDetails.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I really want to do this with Attributes only, if this is possible, anyway with the Fluent-API this works:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<ContactDetails>().HasOne(c => c.CreatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.CreatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.CreatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.UpdatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.UpdatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.UpdatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.DeletedBy).WithOne().HasForeignKey<ContactDetails>(c => c.DeletedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.DeletedById).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByUser).WithOne().HasForeignKey<User>(u => u.ContactDetailsByUserId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByUserId).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByAdmin).WithOne().HasForeignKey<User>(u => u.ContactDetailsByAdminId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByAdminId).IsUnique(false);
}
Maybe the Fluent thing will help someone, but is it possible to do this with Attributes only?
Thanks for your time.
Best Regards
Stewie

Foreign key "The property X on entity type Y could not be found" Entity Framework Core

I've just started working with EF.
I have simple models Page and Related Pages.
public class Page
{
public int ID { get; set; }
[Required]
public string UrlName { get; set; }
[Required]
public string Title { get; set; }
public List<RelatedPages> RelPages1 { get; set; }
public List<RelatedPages> RelPages2 { get; set; }
}
public class RelatedPages
{
public int ID { get; set; }
public int Page1ID { get; set; }
public Page Page1 { get; set; }
public int Page2ID { get; set; }
public Page Page2 { get; set; }
}
This is in my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Page>().HasIndex(x => x.UrlName).IsUnique();
modelBuilder.Entity<RelatedPages>().HasOne(x => x.Page1).WithMany(x => x.RelPages1).HasForeignKey(x => x.Page1ID);
modelBuilder.Entity<RelatedPages>().HasOne(x => x.Page2).WithMany(x => x.RelPages2).HasForeignKey(x => x.Page2ID);
}
Though when I try to execute command
dbContext.Pages.Where(x => x.RelPages1 != null);
I have an exception
InvalidOperationException: The property 'Page1ID' on entity type 'Page' could not be found. Ensure that the property exists and has been included in the model.
This is appears to be a bug in EF Core. You can file issues at https://github.com/aspnet/EntityFramework/issues.
If what you are trying to query for is the set of all pages that have at least one RelPages1, then a LINQ query that doesn't have the same bug and returns the expect result is:
dbContext.Pages.Where(x => x.RelPages1.Count() > 0)

EF 7: How to load related entities in a One-to-many relationship

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(...)...;

Wrong field in table UsersOrders

I'm trying to add some entities using EntityFramework. I need the same model as in image
I created 3 classes:
public class UsersOrders : Entity
{
public int Order_ID { get; set; }
public int User_ID { get; set; }
public virtual User User { get; set; }
public virtual Order Order { get; set; }
}
public class User : Entity
{
public int User_ID { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public string Patronymic { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<UsersOrders> Orders { get; set; }
}
public class Order : Entity
{
public int Order_ID { get; set; }
public virtual Address Address { get; set; }
public virtual User User_ID_Courier { get; set; }
public virtual ICollection<UsersOrders> Users { get; set; }
}
Using FluentAPI trying to set primary keys in my DBContext:
builder.Entity<UsersOrders>()
.HasKey(od => new {od.Order_ID});
It works, but why in DB this field "Order_Order_ID" appears? I'm not sure but I really didn't set this field.
Thanks for any help!
UPD: OnModelCreating
protected override void OnModelCreating(DbModelBuilder builder)
{
//OrderedDishes
builder.Entity<OrderedDishes>().HasKey(od => new { od.Order_ID, od.Dish_ID, od.Number });
builder.Entity<OrderedDishes>().HasRequired(od => od.Order).WithMany(od => od.Dishes).HasForeignKey(od => od.Order_ID);
builder.Entity<OrderedDishes>().HasRequired(od => od.Dish).WithMany(od => od.Orders).HasForeignKey(od => od.Dish_ID);
//OrderStatus
builder.Entity<OrderStatus>().HasKey(os => new { os.Order_ID, os.StatusType_ID });
builder.Entity<OrderStatus>().HasRequired(os => os.Order);
builder.Entity<OrderStatus>().HasRequired(os => os.StatusType);
//DishStatus
builder.Entity<DishStatus>().HasKey(os => new { os.Order_ID, os.Dish_ID, os.Number, os.StatusType_ID });
builder.Entity<DishStatus>().HasRequired(os => os.OrderedDishes);
builder.Entity<DishStatus>().HasRequired(os => os.StatusType);
//user
builder.Entity<UsersOrders>().HasKey(od => new { od.Order_ID });
builder.Entity<UsersOrders>().HasRequired(os => os.User);
builder.Entity<UsersOrders>().HasRequired(os => os.Order);
//PriceOfDish
builder.Entity<PriceOfDish>().HasKey(t => new { t.Dish_ID, t.DateTime });
}
You have the wrong field because you rely on the Code First's convention but your are not following it correctly for the UsersOrders entity. Assuming that Id is the primary key of User class then it will look for UserId (not User_Id) which is not in your UsersOrders entity. The same goes for Order navigational property in UsersOrders.
To fix this you have to follow the convetion by refactoring your UsersOrders and use data annotations like this: (If you use this solution you must remoeve configuration for UsersOrders entity in your OnModelCreating implementation.
public class UsersOrders
{
[Key]
public int OrderID { get; set; }
[Key]
public int UserID { get; set; }
public virtual User User { get; set; }
public virtual Order Order { get; set; }
}
Or in your OnModelCreating implementation make the following changes for UsersOrders entity like this:
modelBuilder.Entity<UsersOrders>().HasKey(od => new { od.Order_ID, od.User_ID });
modelBuilder.Entity<UsersOrders>().HasRequired(os => os.User).WithMany(p => p.Orders).HasForeignKey(p => p.User_ID);
modelBuilder.Entity<UsersOrders>().HasRequired(os => os.Order).WithMany(p => p.Users).HasForeignKey(p => p.Order_ID);
In the two solutions, note that UsersOrders use composite keys using Order_Id and User_Id not only Order_ID.

EF5 Code First Cascade on delete

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

Categories

Resources