Mapping entity framework model to multiple tables - c#

How can I map an entity framework model to multiple tables?
How to perform insertion operation to specific table (by reference of string which stores the table name)?

I have not implemented this but a quick search provides many good examples of a practice known as Entity Splitting. The following should be useful:
http://www.c-sharpcorner.com/UploadFile/ff2f08/entity-splitting-in-entity-framework-6-code-first-approach/
public partial class Employee
{
// These fields come from the “Employee” table
public int EmployeeId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
// These fields come from the “EmployeeDetails” table
public string PhoneNumber { get; set; }
public string EmailAddress { get; set; }
}
public partial class Model : DbContext
{
public Model() : base("name=EntityModel")
{
Database.Log = Console.WriteLine;
}
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Map(map =>
{
map.Properties(p => new
{
p.EmployeeId,
p.Name,
p.Code
});
map.ToTable("Employee");
})
// Map to the Users table
.Map(map =>
{
map.Properties(p => new
{
p.PhoneNumber,
p.EmailAddress
});
map.ToTable("EmployeeDetails");
});
}
}
All credit for the above code goes to linked post

In this case you can implement your own IModelCacheKeyFactory, which allow to hook into the model caching mechanism so EF is able to create different models based on some value right in runtime.
This article explains how

Related

Relationship - EF Core

I'm having some trouble to get into EF Core relationship.
I didn't know how to search it properly, so I've not found what I need, but I got somewhere.
I have these two classes:
Expense:
public class Expense : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public List<ExpenseType> Types { get; set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
ExepenseType:
public class ExpenseType : Entity
{
#region properties
public string Name { get; private set; }
public string Description { get; private set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseTypeIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
During the ToListAsync in ExpenseType, the EF adds the column "expenseId" to the query, but this column does not exist.
My database has three tables, one for each class, and one for the relationship.
(Expense, ExpenseType and Expense_ExpenseType)
By looking for the solution here on StackOverflow I found that I should have a class for the third table.
Here it is:
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them.
So ExpenseType hasn't any Expense.
I'm not sure what I should do now.
Should I Map using optionsBuilder? How?
Should I ReWrite the database?
If you want to create Many-to-Many relationship, you have several options how to do it:
Create additional class how you described. In this case EF will create table and you can get access to get values only from this table.
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
You may don't create class and just describe in the context relationship. Where you will describe everything and EF will create by yourself this table. But from the app you will not see this table. You have to use this variant if you don't want to extend table with additional fields.
modelBuilder
.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
For this relationship you can read more here
But in your case you don't need to use Many-to-Many. That's why if you don't want to add propertie ExpanseTypeId or ExpenseId in your model you can describe it like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Expense>()
.HasMany<ExpenseType>(o => o.Types) //It is your list of expense types.
.WithOne() //Here you can add model for expense. To have an option go back from expense type to expense
.HasForeignKey("ForeignKey");//This key EF will create for you in DB but not in you app model
}
What do you want to use you have to decide. If you have an idea that expense has a lot of expensetypes and each expense type has a lot of expenses. You have to use Many-To-Many how I described.
I think that your main question is "My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them."
so you can do that by creating a nullable foreign key ExpenseTypeId in Expanse class and HashSet of Expanse in ExpeseType class.
Like this:
public class ExpenseType : Entity
{
public ICollection<Expanse> Expanses {get; set;} = new HashSet<Expanse>()
}
public class Expense : Entity
{
public int? ExpanseTypeId {get; set;}
public ExpanseType ExpanseType {get; set;}
}

Entity Framework retrieve objects based in condition

I am developing an Web Api application using Entity Framework where I have these two models and my DbContext class:
public class Course
{
[Key]
public int CourseId { get; set; }
[Required]
public string CourseName{ get; set; }
public ICollection<Students> Students { get; set; }
}
public class Students
{
[Key]
public int StudentId { get; set; }
[Required]
public string StudentName{ get; set; }
[Required]
public int StudentAge{ get; set; }
}
//My CourseContext
public class CourseContext : DbContext
{
public CourseContext () : base("CourseDB") { }
public DbSet<Course> Courses { get; set; }
public DbSet<Students> Students { get; set; }
}
I am modeling a WebAPi with these models above and I need to list all students that are taking a course which means that are added to the ICollection Students property in Course Class. I have tried to develop a solution for that but I have no idea how to do that since I do not have a foreign key property between the models.
...
using (var course_db = new CourseContext ()){
/*but this return all students and repeated ones since one student
can be in more than one course*/
var students= course_db.Course.Include("Students").Select(x=> x.Students);
if (students== null){
return Json(new { success = false });
}
return Json(students, JsonRequestBehavior.AllowGet);
}
...
I have tried the code below but it did not work. I just want to retrieve the students that are enrolled in a course, since some of them are not. Can someone help me?
You need to further build relationships in your model. The student to course relationship should be a many-to-many relationship which you are missing the proper properties in each but also a joining table and appropriate model builder (context) configuration.
Override your dbcontext model creation method to add your fluent configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Build many-to-many relationships
modelBuilder.Entity<Student>()
.HasMany<Course>(c => c.Courses)
.WithMany(s => s.Student)
.Map(pe =>
{
pe.MapLeftKey("Student_ID");
pe.MapRightKey("Course_ID");
pe.ToTable("StudentCoursesTable");
});
}
Add missing Courses from your Student class:
public class Students
{
public ICollection<Courses> Courses { get; set; }
}

Many to Many Self Reference

An user can manage many company. And an user can have an active company at a time from list of many-company. How I can achieve this?
My current model is :
public class User
{
public int Id
public virtual ICollection<Company> Companies { get; set; }
public User()
{
this.Companies = new HashSet<Company>();
}
}
public class Company
{
public int Id
public virtual ICollection<User> Users { get; set; }
public User()
{
this.Users = new HashSet<User>();
}
}
If I add another anotation :
public class User
{
public int CompanyId
public virtual Company Company { get; set; }
}
This model wont referenced to many-to-many table which UserCompany table.
How I can achieve this? Or is there another approach for this case?
Now I'm thinking about make manual many-to-many relationship model and add another ActiveCompany field referenced from custom many-to-many relationship. Is this good approach?
I'm pretty sure you can do this by using the fluent api and overriding the OnModelCreating method in your DbContext, but I don't have an example to hand.
However you might just find it easier to add another property for your user
public int ActiveCompanyId { get; set; }
There are a lot of variables like if you are using lazy loading, how many companies / users there are, what your patterns of data access are, which may determine your best overall approach. If your Companies property is usually or always populated then you could create a get only property that is not mapped:
public class User
{
public int Id
public virtual ICollection<Company> Companies { get; set; }
public int ActiveCompanyId { get; set; }
[NotMapped]
public Company ActiveCompany
{
get
{
return this.Companys == null ? null :
Companies.Where(x => x.Id == this.Active.CompanyId)
.SingleOrDefault;
}
}
public User()
{
this.Companies = new HashSet<Company>();
}
}
Can you try this mapping :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Users)
.WithMany(u => u.Comapnies)
.Map(x =>
{
x.MapLeftKey("CompanyId");
x.MapRightKey("UserId");
x.ToTable("UserCompany");
});
modelBuilder.Entity<User>()
.HasRequired(b => b.Company)
.WithMany()
.WillCascadeOnDelete(false);
}

Entity Framework - Link Table Name Issue

I have a table called "Articles" and then a linking table called "RelatedArticles".
The RelatedArticles table simply maps an article to another article. Entity Framework is looking for the table "ArticleArticles". What can I put in the OnModelCreating method that tells Entity Framework the actual name of this linking table?
Table RelatedArticles {
int articleId
int relatedArticleId
}
There is no class "RelatedArticles". Only a List property in the class Article:
public class Article
{
public Article()
{
Links = new HashSet<Link>();
RelatedArticles = new HashSet<Article>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public System.DateTime Created { get; set; }
public virtual ICollection<Link> Links { get; set; }
public virtual ICollection<Article> RelatedArticles { get; set; }
}
You can override OnModelCreating to specify the table and column names:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasMany(a => a.RelatedArticles)
.WithMany()
.Map(t => t.MapLeftKey("articleId")
.MapRightKey("relatedArticleId")
.ToTable("RelatedArticles"));
}
If it were a class you could use the attribute [Table(Name = "RelatedArticles"]. See the docs about TableAttribute on MSDN.
You've mentioned you don't, so I think you want this:
modelBuilder
.Entity<Article>
.HasMany(a => a.RelatedArticles)
.WithMany()
.Map(a => a.ToTable("RelatedArticles");

Optional navigation property is not being loaded

I can't for the life of me figure out why Entity Framework (4.1, Code First) is not materializing an optional navigation property.
POCOs:
public class PlanMember {
public Int64 Id { get; set; }
public String FirstName { get; set; }
public virtual SystemUser CaseManager { get; set; }
public String LastName { get; set; }
public virtual PlanMemberStatus Status { get; set; }
}
public class SystemUser {
public String EMail { get; set; }
public String FirstName { get; set; }
public String Id { get; set; }
public String LastName { get; set; }
}
public class PlanMemberStatus {
public Int32 Id { get; set; }
public String Code { get; set; }
}
Configuration Classes:
public class ForPlanMemberEntities:EntityTypeConfiguration<PlanMember> {
public ForPlanMemberEntities(String schemaName) {
this.HasOptional(e => e.CaseManager)
.WithMany()
.Map(m => m.MapKey("CaseManagerID"));
this.HasRequired(e => e.Status)
.WithMany()
.Map(m => m.MapKey("StatusID"));
this.ToTable("PlanMember", schemaName);
}
}
public class ForSystemUserEntities:EntityTypeConfiguration<SystemUser> {
public ForSystemUserEntities(String schemaName) {
this.ToTable("SystemUser", schemaName);
}
}
public class ForPlanMemberStatusEntities:EntityTypeConfiguration<PlanMemberStatus> {
public ForPlanMemberStatusEntities(String schemaName) {
this.ToTable("PlanMemberStatus", schemaName);
}
}
Context:
public class Context:DbContext {
public DbSet<PlanMember> PlanMemberDbSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
var schemaName = Properties.Settings.Default.SchemaName;
modelBuilder.Configurations
.Add(new Configuration.ForPlanMemberEntities(schemaName))
.Add(new Configuration.ForPlanMemberStatusEntities(schemaName))
.Add(new Configuration.ForSystemUserEntities(schemaName))
;
}
}
Based on that configuration, if I run a query like the following it will not populate the CaseManager property. The PlanMemberStatus property loads just fine.
var test = (from member in PlanMemberDbSet
where member.CaseManager != null
select member).First();
var cm = test.CaseManager;
In my example cm is always null, despite the fact that - logically speaking - it should be impossible. Any thoughts?
EDIT - If I do PlanMemberDbSet.Include("CaseManager") then it works as expected. Why is this necessary? In other scenarios I haven't had to do this.
My guess is that the CaseManagerID foreign key column value in the PlanMember table is different to the Id primary key column value in table SystemUser - different for .NET but not for SQL Server (with standard sorting system), for example different in casing: "a" in CaseManagerID but "A" in Id.
Querying for != null (translates into IS NOT NULL in SQL) works, so you get indeed only the PlanMember entities which have a SystemUser assigned in the database. Also the query by the CaseManager (triggered by lazy loading) is correctly executed in the database (INNER JOIN) and transmitted to the client. Even the object materialization works (there are 2 objects in the context). But EF fails to relate the loaded PlanMember with the loaded CaseManager because the keys are not identical (for example with respect to capital/small letters). As a result your navigation property is null.
This doesn't occur with the Status navigation property because the key is an Int32.
The problem looks closely related to this one: EF 4.1 Code First bug in Find/Update. Any workaround? Should it be reported?

Categories