Entity Framework Optional Relationship Mapping - entity-framework

I have three POCO entity classes:
public class User {
public int Id { get; set; }
public string UserName { get; set; }
}
public class BlogPost {
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<BlogComment> Comments { get; set; }
}
public class BlogComment {
public int Id { get; set; }
public int PostId { get; set;
public int? UserId { get; set; }
public string Text { get; set; }
public virtual BlogPost Post { get; set; }
public virtual User User { get; set; }
}
Couple of points to highlight:
From the User end, it doesn't know nor care about BlogComments. The user entity may be referenced by a great many number of entities across the project. It is important to be able to reference the User from these entities, but not from the User end of the relationship.
The BlogComment may or may not have a reference to a User, it will depend upon whether the Comment was made by an authenticated user or not.
In my Entity Framework configuration, I define the relationship between BlogPost and BlogComment from the BlogComment end:
HasRequired(t => t.Post)
.WithMany(t => t.Comments)
.HasForeignKey(t => t.PostId);
But I am struggling to understand how I construct the optional reference to the User:
HasOptional(t => t.User)
// What comes next, and why?
Any advice would be appreciated.

Related

EF core 2.0 one to many share the same table

How to resolve "Navigation properties can only participate in a single relationship." error on below case?
1 company has many Milestone and MissionValueStory, where Milestone and MissionValueStory share same table with different typeId, and each of those has many translation where link up with companyInfoId only
Or BETTER break the relationship between companyInfo and company, and just another query to fetch companyInfo is much easy?
public class Company
{
[key]
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<CompanyInfo> Milestone { get; set; } //multi
public virtual ICollection<CompanyInfo> MissionValueStory { get; set; } //multi
}
public class CompanyInfo
{
[key]
public long Id { get; set; }
public long typeId { get; set; }
[Required]
public long CompanyId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public ICollection<Translation> Translation { get; set; }
}
public class Translation
{
[key]
public long Id { get; set; }
public string Title { get; set; }
[Required]
public long CompanyInfoId { get; set; }
public string Language { get; set; }
[ForeignKey("CompanyInfoId")]
public virtual CompanyInfo CompanyInfo { get; set; }
}
modelBuilder.Entity<Company>()
.HasMany(e => e.Milestone)
.WithOne(t => t.Company)
.HasForeignKey(m => m.CompanyId).IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Company>()
.HasMany(e => e.MissionValueStory)
.WithOne(t => t.Company)
.HasForeignKey(m => m.CompanyId).IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CompanyInfo>()
.HasMany(e => e.Translation)
.WithOne(t => t.CompanyInfo).IsRequired();
What you're trying to do is legitimately not supported. At least in the way you're going about this. Fortunately there's a fairly painless solution for you. Use Table Per Hierarchy.
Change the class CompanyInfo to be an abstract class called CompanyInfoBase, and let it be an abstract type. Make typeId abstract on CompanyInfoBase.
Create two new classes that implement CompanyInfoBase:
public class MilestoneCompanyInfo : CompanyInfoBase
{
public override long typeId { get; set; } = MILESTONE_TYPE_ID;
}
public class MissionValueStoryCompanyInfo : CompanyInfoBase
{
public override long typeId { get; set; } = MISSION_VALUE_STORY_TYPE_ID;
}
where MILESTONE_TYPE_ID and MISSION_VALUE_STORY_TYPE_ID are some sort of predefined constants.
Then, in your DbContext's OnModelCreating, use typeId as your discriminator.
It'll look something like this:
modelBuilder.Entity<CompanyInfoBase>()
.HasDiscriminator<long>(nameof(CompanyInfoBase.typeId))
.HasValue<MilestoneCompanyInfo>(MILESTONE_TYPE_ID)
.HasValue<MissionValueStoryCompanyInfo>(MISSION_VALUE_STORY_TYPE_ID);
Since you're changing the name of the entity, it's worth setting the table name to accommodate your existing db. Something like:
modelBuilder.Entity<CompanyInfoBase>().ToTable("CompanyInfos");
Note to other readers: It's only required to define the discriminator like this due to his decision to use a long. If he had just left it undefined then EF Core automagically handles this (by creating a column named discriminator that contains the concrete class names).
Here's a link to the inheritance reference page: https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/inheritance

MVC EF code first creating model class

I'm new to MVC and EF code first. I'm in struggle to model a real-estate company DB model using EF code-first approach and I did some exercises as well as reading some online tutorials.
First thing I have a customers table that would be in relation with one or more properties he/she has registered as it's owner to sell or to rent, I was wondering if it is possible to have some sub classes inside a model class for registered properties as below:
public Property
{
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public class Apartment{
public int ApartmentID { get; set; }
public int AptSqureMeter { get; set; }
. . .
. . .
}
public class Villa{
public int VillaID { get; set; }
public int VillaSqureMeter { get; set; }
. . .
. . .
}
and also other sub-classes for other types of properties
}
If the answer is Yes, then how should I declare the relations using data annotation or Fluent API, and then please help me how to update both Customers table and Property table with the customer information and property info at the same time?
thanks for your answer in advance.
As #Esteban already provided you with a pretty detailed answer on how to design your POCOs and manage the relationship between them, I will only focus on that part of your question:
how should I declare the relations using data annotation or Fluent API
First of all, you should know that certain model configurations can only be done using the fluent API, here's a non exhaustive list:
The precision of a DateTime property
The precision and scale of numeric properties
A String or Binary property as fixed-length
A String property as non-unicode
The on-delete behavior of relationships
Advanced mapping strategies
That said, I'm not telling you to use Fluent API instead of Data Annotation :-)
As you seem to work on an MVC application, you should keep in mind that Data Annotation attributes will be understood and processed by both by Entity Framework and by MVC for validation purposes. But MVC won't understand the Fluent API configuration!
Both your Villa and Apartment classes have similar properties, if they are the same but as it's type, you could create an enum for that.
public enum PropertyType {
Apartment = 1,
Villa
}
public class Property {
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public PropertyType PropertyType { get; set; }
public int SquareMeter { get; set; }
}
This way of modelating objects is refered as plain old clr object or POCO for short.
Assume this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role {
public int RoleId { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
}
Creating relations with fluent api:
Mapping many to many
On your OnModelCreating method (you'll get this virtual method when deriving from DbContext):
protected override void OnModelCreating(DbModelBuilder builder) {
// Map models/table
builder.Entity<User>().ToTable("Users");
builder.Entity<Role>().ToTable("Roles");
// Map properties/columns
builder.Entity<User>().Property(q => q.UserId).HasColumnName("UserId");
builder.Entity<User>().Property(q => q.Username).HasColumnName("Username");
builder.Entity<Role>().Property(q => q.RoleId).HasColumnName("RoleId");
builder.Entity<Role>().Property(q => q.Name).HasColumnName("Name");
// Map primary keys
builder.Entity<User>().HasKey(q => q.UserId);
builder.Entity<Role>().HasKey(q => q.RoleId);
// Map foreign keys/navigation properties
// in this case is a many to many relationship
modelBuilder.Entity<User>()
.HasMany(q => q.Roles)
.WithMany(q => q.Users)
.Map(
q => {
q.ToTable("UserRoles");
q.MapLeftKey("UserId");
q.MapRightKey("RoleId");
});
Mapping different types of relationships with fluent api:
One to zero or one:
Given this model:
public class MenuItem {
public int MenuItemId { get; set; }
public string Name { get; set; }
public int? ParentMenuItemId { get; set; }
public MenuItem ParentMenuItem { get; set; }
}
And you want to express this relationship, you could do this inside your OnModelCreating method:
builder.Entity<MenuItem>()
.HasOptional(q => q.ParentMenuItem)
.WithMany()
.HasForeignKey(q => q.ParentMenuItemId);
One to many
Given this model:
public class Country {
public int CountryId { get; set; }
public string Name { get; set; }
public virtual List<Province> Provinces { get; set; }
}
public class Province {
public int ProvinceId { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
You now might want to express this almost obvious relationship. You could to as follows:
builder.Entity<Province>()
.HasRequired(q => q.Country)
.WithMany(q => q.Provinces)
.HasForeignKey(q => q.CountryId);
Here are two useful links from MSDN for further info:
Configuring Relationships with the Fluent API.
Code First Relationships Fluent API.
EDIT:
I forgot to mention how to create a many to many relationship with additional properties, in this case EF will NOT handle the creation of the join table.
Given this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
pubilc virtual List<UserEmail> UserEmails { get; set; }
}
pubilc class Email {
public int EmailId { get; set; }
public string Address { get; set; }
public List<UserEmail> UserEmails { get; set; }
}
public class UserEmail {
public int UserId { get; set; }
public int EmailId { get; set; }
public bool IsPrimary { get; set; }
public User User { get; set; }
public Email Email { get; set; }
}
Now that we've added a new property into our join table ef will not handle this new table.
We can achieve this using the fluent api in this case:
builder.Entity<UserEmail>()
.HasKey( q => new {
q.UserId, q.EmailId
});
builder.Entity<UserEmail>()
.HasRequired(q => q.User)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.EmailId);
builder.Entity<UserEmail>()
.HasRequired(q => q.Email)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.UserId);

How to map two properties in one code first object to the same parent type

I've been at this for hours and have tried many suggestions I found searching but no luck. I'm using code first EF 5.
The situation is that I have a class Employee. Then I have another class that has two properties on it, both are of type Employee. I want these both to be foreign key constraints but the requirements allow many of the same requests to and from the same users so I can't just use them as keys. I don't really care about Employee having the two collections for navigation but in my working through the problem that seemed a requirement. If it simplifies the problem I can remove those.
I get this message.
System.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'Employee_RequestsForEmployee_Target' in relationship 'Employee_RequestsForEmployee'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
I've tried this using the Fluent API in the OnModelCreation method of the context;
modelBuilder.Entity()
.HasRequired(u => u.ForEmployee)
.WithMany()
.HasForeignKey(u => u.ForEmployeeId);
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(u => u.FromEmployee)
.WithMany()
.HasForeignKey(u => u.FromEmployeeId);
The classes in conflict are (I've removed some properties for clarity);
public class Employee : IEmployee
{
[Key]
public string Id { get; set; }
[InverseProperty("ForEmployee")]
public ICollection<RevenueTransferRequest> RequestsForEmployee { get; set; }
[InverseProperty("FromEmployee")]
public ICollection<RevenueTransferRequest> RequestsFromEmployee { get; set; }
}
public class RevenueTransferRequest : IRevenueTransferRequest
{
[Key]
public Guid Id { get; set; }
[Required]
[ForeignKey("ForEmployee")]
public String ForEmployeeId { get; set; }
[InverseProperty("RequestsForEmployee")]
public Employee ForEmployee { get; set; }
[Required]
[ForeignKey("FromEmployee")]
public String FromEmployeeId { get; set; }
[InverseProperty("RequestsFromEmployee")]
public Employee FromEmployee { get; set; }
}
Any help would be much appreciated. Thanks in advance.
I never did figure out how to do it using data annotations but using the Fluent API I was able to do it. What I was missing was that I had to specify in the HasMany() method what the relationship on the other side was which I assumed was understood through the data annotations and conventions.
This is called in the DbContext OnModelCreating override (The WillCascadeOnDelete(false) is related to another issue).
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(e => e.FromEmployee)
.WithMany(x=>x.RequestsFromEmployee)
.WillCascadeOnDelete(false);
modelBuilder.Entity<RevenueTransferRequest>()
.HasRequired(e => e.ForEmployee)
.WithMany(x => x.RequestsForEmployee)
.WillCascadeOnDelete(false);
With the classes:
[Key]
public String Id { get; set; }
public String BusinessUnitLeaderId { get; set; }
public Employee BusinessUnitLeader { get; set; }
[Required]
[MaxLength(150)]
public String DisplayName { get; set; }
public ICollection<Project> BusinessUnitLeaderProjects { get; set; }
public ICollection<RevenueTransferRequest> RequestsForEmployee { get; set; }
public ICollection<RevenueTransferRequest> RequestsFromEmployee { get; set; }
public class RevenueTransferRequest
{
[Key]
public Guid Id { get; set; }
[Required]
public String ForEmployeeId { get; set; }
public Employee ForEmployee { get; set; }
[Required]
public String FromEmployeeId { get; set; }
public Employee FromEmployee { get; set; }
[Required]
public String ProjectId { get; set; }
public Project Project { get; set; }
[Required]
public Double? TransferAmount { get; set; }
public int WorkflowState { get; set; }
}

Defining foreign key constraints with Entity Framework code-first

I have following entity class called Code. It stores categories of different kinds - the data for which I would have otherwise needed to create many small tables e.g. User Categories, Expense Categories, Address types, User Types, file formats etc.
public class Code
{
public int Id { get; set; }
public string CodeType { get; set; }
public string CodeDescription { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
:
: // many more
}
The class Expense looks like this:
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public double Amount { get; set; }
public DateTime ExpenseDate { get; set; }
}
With the above class definitions, I have established 1:many relation between Code and Expense using the CategoryId mapping.
My problem is, I want to map the SourceId field in Expense to the Code object. Which means, Expense object would contain
public Code Source { get; set; }
If I use this, at runtime I get an error about cyclic dependencies.
Can someone please help?
You will need to disable cascading delete on at least one of the two relationships (or both). EF enables cascading delete by convention for both relationships because both are required since the foreign key properties are not nullable. But SQL Server doesn't accept multiple cascading delete paths onto the same table that are introduced by the two relationships. That's the reason for your exception.
You must override the convention with Fluent API:
public class Code
{
public int Id { get; set; }
//...
public virtual ICollection<Expense> Expenses { get; set; }
//...
}
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public virtual Code Source { get; set; }
//...
}
Mapping with Fluent API;
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Category)
.WithMany(c => c.Expenses)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Source)
.WithMany()
.HasForeignKey(e => e.SourceId)
.WillCascadeOnDelete(false);

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table