Relationship - EF Core - c#

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

Related

Navigation property are null when querying parent entity

Below is my base class for Domain entities:
public interface IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class BaseEntity : IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class ExternalSystem : BaseEntity
{
public string Name { get; set; }
public string ConnectionUrl { get; set; }
public ICollection<ExternalSystemRules> ExternalSystemRules { get; set; }
}
public ExternalSystemRules : BaseEntity
{
public string RuleName { get; set; }
public string ConfiguredBy { get; set; }
public int ExternalSystemId { get; set; }
public ExternalSystem ExternalSystem { get; set; }
public ICollection<TaskSchedular> TaskSchedulars { get; set; }
}
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
}
}
public class ExternalSystemRulesConfiguration : IEntityTypeConfiguration<ExternalSystemRules>
{
public void Configure(EntityTypeBuilder<ExternalSystemRules> builder)
{
builder.ToTable("ExternalSystemRules");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemRuleId");
builder.HasKey(e=>e.Id);
builder.HasOne(d=>d.ExternalSystem)
.WithMany(p=>p.ExternalSystemRules)
.HasForeignKey(p=>p.ExternalSystemId)
.HasConstraintName("FK_ExternalSystemRules_ExternalSystemId");
builder.Navigation(p=>p.ExternalSystem)
.IsRequired()
.AutoInclude();
}
}
public class MyDatabaseContext : DbContext
{
private readonly IConfiguration _configuration;
public MyDatabaseContext(IConfiguration configuration)
{
_configuration = configuration;
Database.EnsureCreated();
}
public DbSet<ExternalSystem> ExternalSystem {get; set; }
public DbSet<ExternalSystemRules> ExternalSystemRule {get; set; }
public void Save()
{
this.SaveChanges();
}
}
I already had existing database created so I created all this domain models and configuration based on existing database tables and relationships.
Now when I am trying to get list of ExternalSystems like below :
var myDatabaseContext = new MyDatabaseContext();
var externalSystems = myDatabaseContext.ExternalSystem.ToList();
This returns the list of ExternalSystems but my "ExternalSystemRules" navigation property is null. All the other related child entities are null as well.
Now, I don't want to explicitly keep on using .Include() to load related entities. I want to use default feature of entity framework core of eager loading other related entities when querying parent entity.
What might be the problem here?
Database table:
ExternalSystem:
ExternalSystemId(PK) Name ConnectionUrl
ExternalSystemRules:
ExternalSystemRuleId(PK) RuleName ConfiguredBy ExternalSystemId(F.K)
This isn't a problem with your configuration, but how you query your data. You should manually include the relational record / navigation properties, you are looking for:
var externalSystems = myDatabaseContext
.ExternalSystem
.Include(es => es.ExternalSystemRules)
.ToList();
I would advise against using AutoInclude, though (*). While it might not seem to make a difference, when you have tens or hundreds of objects to query, performance will degrade very rapidly, once your dataset grows. Everytime you query a set with AutoInclude enabled, you will get all its navigation properties with it, whether you need it, or not. In addition, this will also apply, to all entity-types derived from this entity. If you decide to use it anyway, you can disable it for single queries by using .IgnoreAutoIncludes().
A problem common to both approaches (Include(), as well as AutoInclude()) are hierachies. They will work for very simple models, but once you try to map a hierachy - think of something like a tree, where a Rule can have sub-rules of the same type, you might run into problems with self-referencing loops and need to manually project.
There is a nice article explaining the problem here: https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
A better way of querying your data, would be to use "view models", as to avoid returing unused or sensitive data to your clients.
public class ExternalSytemVm
{
public int Id {get; set;}
public IEnumerable<ExternalSystemRulesVm> Rules {get; set;}
/* ...*/
}
public class ExternalSytemRulesVm
{
public int Id {get; set;}
public string Name {get; set;}
/* ...*/
}
var externalSystems = myDatabaseContext
.ExternalSystem
.Select(es => new ExternalSystemVm {
Id = es.Id
Rules = es.ExternalSystemRules.Select(esr => {
/* ... */
})
})
.ToList();
(*) If you are absolutely sure, that you will always need all of your properties, with all of the navigation properties in each and every query in your application, this might be fine.
Documentation: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager
Add the AutoInclude() to your Fluent API configuration as follows (similar to what you already have for ExternalSystemRules):
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
builder.Navigation(e => e.ExternalSystemRules)
.AutoInclude();
}
}

EF6: Include over nested TPH structure in SQL Server

Hello everybody and a happy new 2017,
I have the following table-/object structure.
[Table("Table1")]
public class Table1
{
[Key]
public long Table1Id { get; set; }
public virtual ICollection<Table2> ItemsOfTable2 { get; set; }
}
[Table("Table2")]
public class Table2
{
[Key]
public long Table2Id { get; set; }
public long Table1Id { get; set; }
[ForeignKey("Table1Id")]
public virtual Table1 Table1Object { get; set; }
public virtual ICollection<Table3Base> ItemsOfTable3 { get; set; }
[NotMapped]
public virtual ICollection<Table3Red> RedItems
{
get { return this.ItemsOfTable3.OfType<Table3Red>().ToList(); }
}
[NotMapped]
public virtual ICollection<Table3Blue> BlueItems
{
get { return this.ItemsOfTable3.OfType<Table3Blue>().ToList(); }
}
}
[Table("Table3Base")]
public abstract class Table3Base
{
[Key]
public long Table3Id { get; set; }
public long Table2Id { get; set; }
[ForeignKey("Table2Id")]
public virtual Table2 Table2Object { get; set; }
}
public class Table3Red : Table3Base
{
public string SpecialPropertyForRed { get; set; }
}
public class Table3Blue : Table3Base
{
public int SpecialPropertyForBlue { get; set; }
public virtual ICollection<Table4> ItemsOfTable4 { get; set; }
}
[Table("Table4")]
public class Table4
{
[Key]
public long Table4Id { get; set; }
public long Table3Id { get; set; }
[ForeignKey("Table3Id")]
public virtual Table3Blue Table3BlueObject { get; set; }
}
public class MyContext : DbContext
{
public virtual IDbSet<Table1> Table1DbSet { get; set; }
public virtual IDbSet<Table2> Table2DbSet { get; set; }
public virtual IDbSet<Table3Red> Table3RedDbSet { get; set; }
public virtual IDbSet<Table3Blue> Table3BlueDbSet { get; set; }
public virtual IDbSet<Table4> Table4DbSet { get; set; }
}
So, in the middle of this "tree", there is a TPH structure (classes Table3Base, Table3Red, Table3Blue stored in database table "Table3Base"). And we only have IDbSets for Table3Red and Table3Blue, not for Table3Base. Every object has a collection navigation property of the next table objects.
Class Table3Blue has another collection navigation property to items of Table4 objects.
As further (but hopefully irrelevant) information: The default discriminator is mapped to an internal enum:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// TPH: Map Standard-Discriminator to Enum
modelBuilder.Entity<Table3Base>()
.Map<Table3Red>(m => m.Requires("Typ").HasValue((int)Table3TypEnum.Red))
.Map<Table3Blue>(m => m.Requires("Typ").HasValue((int)Table3TypEnum.Blue));
}
Due to performance issues (loading every single record one by one is very slow; Lazy Loading is active), we want to read this structure from Table1 to Table4 via include like this:
var table1Records = this.m_Context.Table1DbSet
.Include(t => t.ItemsOfTable2)
.Include(t => t.ItemsOfTable2.Select(t2 => t2.ItemsOfTable3))
.Include(t => t.ItemsOfTable2.Select(t2 => t2.ItemsOfTable3.OfType<Table3Blue>().Select(t3 => t3.ItemsOfTable4)))
.ToList();
The first and the second include seem to work, but the third include throws an Argument exception "The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parametername: path".
What am I doing wrong? How do I include Table4-objects on my way to the database?
Kind regards, Mate
This is our workaround. I doubt this is the best solution, so better ways are highly appreciated...
Table3Base gets the collection navigation property to Table4 as a virtual property.
Table3Red (the object without Table4-objects) overrides this property with a getter returning an empty list of Table4 objects and no setter.
Therefore, we can cascade our Include down to Table4 without any type checks. There are no unnecessary records in our PTH database table "Table3Base". So everything is fine, except the clumsy definition of Table3Red with an unused navigation property. :-(
BTW: Include with a long path includes all objects along this path, so the explicit ".Include(A).Include(A.B).Include(A.B.C)" is not necessary; ".Include(A.B.C)" will do the same. The iterating .Include in the code sample is for clarity.
HTH, Mate

How do I create a Many-to-Many relationship with only one entity?

Courses have many prerequisites, and simultaneously a particular course can be a prerequisite for many courses. I've tried to establish the many-to-many relationship (in OnModelBCreating) using EF code-first with the following:
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany(e => e.Postrequisites)
.Map(m => m.ToTable("CourseRequisiteMappings")
.MapLeftKey("CourseId").MapRightKey("CourseId")); // EDIT: THIS LINE IS THE PROBLEM. SEE MARKED ANSWER AND MY COMMENT ON IT.
Also, here is the Course class:
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<Course> Prerequisites { get; set; }
public virtual ICollection<Course> Postrequisites { get; set; }
}
When I implemented this and went to update the database, it gave me the following errors:
CourseId: Name: Each property name in a type must be unique. Property
name 'CourseId' is already defined.
ModuleId: Name: Each property name in a type must be unique. Property
name 'ModuleId' is already defined.
CourseCourse: EntityType: EntitySet 'CourseCourse' is based on type
'CourseCourse' that has no keys defined.
ModuleModule: EntityType: EntitySet 'ModuleModule' is based on type
'ModuleModule' that has no keys defined.
I could not find an example of doing this which leads me to believe one of the following three are true:
There's a different way of accomplishing this that I don't see
I'm on the right track but overlooking something due to my lack of knowledge with EF
I'm the first one to try and EF doesn't support this (very unlikely)
First, does anyone know how I can set up this relationship, i.e., what do these errors mean (responding to #2)? For bonus points, is there another way of doing this that might be better or worse (kinda #1)? Thanks in advance.
Your mapping is nearly correct. But you have to understand that under the hood the Entity Framework wil create a so callled junction table that stores the many to many relationship.
This junction table wil just have two fields, containing the foreign keys which together make up the primary key. Obviously these foreign keys cannot have the same name.EF is smart enough to all figure it out by itself and no maping is necessary. Below a working example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace ManyToManyUnderTheHoodSpike
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<CourseContext>());
using (CourseContext context=new CourseContext())
{
context.Courses.Add(new Course("Top of the bill")
{
PrerequisiteCourses = new List<Course>()
{
new Course("My two cents"),
new Course("Counting to two")
}
});
context.SaveChanges();
}
}
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class Course
{
public Course() { }
public Course(string name)
{
Name = name;
}
public string Name {get;set;}
public int CourseId{get;set;}
public ICollection<Course> PrerequisiteCourses{get;set;}
public ICollection<Course> FollowUpCourses{get;set;}
}
}
If you run this code you get a database with two tables: Courses and CourseCourses with as the only fields Course_Id and Course_Id1.
But that is not very readable, so let's make the mapping to make it more readable:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>().HasMany(course => course.PrerequisiteCourses)
.WithMany(course => course.FollowUpCourses)
.Map(data => data.ToTable("Prerequisites")
.MapLeftKey("FollowUpId")
.MapRightKey("PrerequisiteId"));
}
Presto!
I would model like this. I know you wanted only 1 table. But Ef will create the many to many table if you dont. Not sure what you didnt get right without testing. So anyway, here is another option.
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
//nav elements
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<PreReqCourse> Prerequisites { get; set; }
// You can Find follow on courses, by accessing PreReqCourse table, but if you felt this navigation offered enough value, create a post req table too. Using same approach.
// public virtual ICollection<Course> Postrequisites { get; set; }
}
public class PreReqCourse
{
public virtual int Id {get; set;}
public virtual int CourseId { get; set; }
public virtual Course PreReqForCourse { get; set; } //Nav prop
}
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany();
// Leave WithMany empty. You can define in PreReqCourse Table model, you dont need to model from both directions.
modelBuilder.Entity<PreReqCourse>()
.HasRequired(e => e.PreReqForCourse)
.HasForeignKey(f => f.CourseId)
.WithMany(p=>p.PreRequisites);

EF5 Model-First, DBContext code generation and derived class

I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();

Entity Framework Many to many through containing object

I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");

Categories