EF many-to-many can't load one end - entity-framework

I have the code as below.
class Student : IPeople
{
private string name;
public string Name
{
get { return name;}
set { name = value;}
}
private bool sex;
public bool Sex
{
get{ return sex; }
set{ sex = value;}
}
private int age;
public int Age
{
get{return age;}
set{age = value;}
}
public virtual ICollection<Dog> dogs { get;set; }
public Student()
{
dogs = new List<Dog>();
}
}
class Pet
{
string Name { get; set; }
bool Sex { get; set; }
int Age{get;set;}
}
class Dog : Pet
{
public string Type { get; set; }
public virtual ICollection<IPeople> persons { get; set; }
public Dog()
{
persons = new List<IPeople>();
}
}
The context is
class TestContext : DbContext
{
public DbSet<Student> studentSet { get; set; }
public DbSet<Dog> dogSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasMany(x => x.dogs).WithMany(y => (ICollection<Student>)y.persons);
}
}
If I insert the records likes below,
using (TestContext context = new TestContext())
{
Student s = new Student();
s.Age = 18;
s.Sex = true;
s.Name = "ts";
Dog d = new Dog();
d.Type = "abc";
d.Sex = false;
d.Name = "dog";
d.Age = 3;
s.dogs.Add(d);
context.studentSet.Add(s);
context.SaveChanges();
}
everything works well, but if I insert the records likes below, the Student record will not insert into database.
using (TestContext context = new TestContext())
{
Student s = new Student();
s.Age = 18;
s.Sex = true;
s.Name = "ts";
Dog d = new Dog();
d.Type = "abc";
d.Sex = false;
d.Name = "dog";
d.Age = 3;
d.persons.Add(s);
context.dogSet.Add(d);
context.SaveChanges();
}
Anyone can help?

You can't use the interface IPeople here:
public virtual ICollection<IPeople> persons { get; set; }
Navigation properties must refer to entity classes - either abstract or concrete - of your model.
A possible alternative might be to use an abstract class People instead of an interface. But you have to put the navigation property...
public virtual ICollection<Dog> dogs { get;set; }
...into that abstract class, not into the derived Student class, because Dog.persons refers to the abstract class People, something like:
abstract class People
{
// ...
public virtual ICollection<Dog> dogs { get;set; }
}
class Student : People
{
// ...
}
class Pet
{
// ...
}
class Dog : Pet
{
// ...
public virtual ICollection<People> persons { get; set; }
}
And the mapping would be:
modelBuilder.Entity<People>()
.HasMany(x => x.dogs)
.WithMany(y => y.persons)
.Map(m =>
{
m.ToTable("PeoplesDogs");
m.MapLeftKey("PeopleId");
m.MapRightKey("DogId");
});

Related

EF Core 6 Seeding Data Gives Foreign Key error Despite Having FK Value

So, I have struggled with this for a while now and can't figure out what I'm missing. I have a table that holds an entity called Skill and the DataModel looks like this:
public class SkillModel
{
public SkillModel()
{
}
public SkillModel(int skillId)
{
SkillId = skillId;
}
public int SkillId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public int SkillGroupId { get; set; } = 0;
public SkillGroupModel SkillGroup { get; set; } = new();
}
It references the SkillGroup which is it's own table and it looks like this:
public class SkillGroupModel
{
public SkillGroupModel()
{
}
public SkillGroupModel(int skillGroupId)
{
SkillGroupId = skillGroupId;
}
public int SkillGroupId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public List<SkillModel> Skills { get; set; } = new();
}
They each have their own configuration files and the look like this:
SkillModel
public class SkillConfiguration : IEntityTypeConfiguration<SkillModel>
{
public void Configure(EntityTypeBuilder<SkillModel> builder)
{
var dataSeeds = new DataSeeds();
builder.ToTable("Skills", "Skills");
builder.HasKey(k => k.SkillId);
builder.HasOne(s => s.SkillGroup)
.WithMany(s => s.Skills);
builder.HasData(dataSeeds.Skills);
}
}
SkillGroupModel
var dataSeeds = new DataSeeds();
builder.ToTable("SkillGroups", "Skills")
.HasKey(k => k.SkillGroupId);
builder.HasData(dataSeeds.SkillGroups);
Data seeds looks like this:
SkillGroupModel Seed
public List<SkillGroupModel> GetSkillGroups()
{
return new List<SkillGroupModel>()
{
new()
{
SkillGroupId = 1, Name = "Artisan", DescriptionId = SkillGroupDescriptions["Artisan"].Id
},
...
}
SkillModel Seeds
return new List<SkillModel>()
{
new()
{
SkillId = 1,
Name = "Aesthetics",
DescriptionId = SkillDescriptions["Aesthetics"].Id,
SkillGroupId = 1
},
...
}
So, I was obviously missing something. Ivan Stoev had a great point of not initializing my navigation properties like that, and that was a great help.
I went about it by not using my navigation properties and only setting the FK Properties. I think in the past I was trying to do both and that was causing issues that took me down this path. I'm not sure what I was doing wrong before but the way the documentation for seeding data on MSDN worked fine for me after going back and trying it again.

Map an Entity iEnumerator To Dto Enumerator

I am using CQRS. I select my Entities IEnumerator from database and i want to map this to my Dto class.
My Dto class:
public class XCollectionDto
{
public IEnumerable<XReadDto> Entries { get; init; } = Enumerable.Empty<XReadDto>();
}
My mapper class:
public class XReadMapper : IEntityToDtoMapper<X, XCollectionDto>
{
public XCollectionDto Map(IEnumerable <X> source, XCollectionDto target)
{
//todo
Here i want to map source to target Entries list
}
}
How can i do that, without a for loop? I am not using AutoMaper, the mapping is manual
I think you could accompish your purpose with C# reflection
I created the two class for test:
public class somemodel
{
public int Id { get; set; }
public string Name { get; set; }
public List<int> Numlist { get; set; }
}
public class somemodelDTO
{
public int Id { get; set; }
public string SomeName { get; set; }
public List<int> Numlist { get; set; }
}
the method to bind properties of somemodelDTO which have the same name with properties of somemodel:
private static somemodelDTO GetMap<somemodel, somemodelDTO>(somemodel some)
{
somemodelDTO somemDTO = Activator.CreateInstance<somemodelDTO>();
var typesource = some.GetType();
var typedestination = typeof(somemodelDTO);
foreach(var sp in typesource.GetProperties())
{
foreach( var dp in typedestination.GetProperties())
{
if(sp.Name==dp.Name)
{
dp.SetValue(somemDTO, sp.GetValue(some, null), null);
}
}
}
return somemDTO;
}
The result:

EF 6.1.3 how to seed on-to-many relationship

I am trying to seed my EF code first database in the Configuration file of the migrations folder, but somehow I have no idea how as I do not want to add a new entry to the related database.
I have the following database structure.
public class ClassSchedule
{
public int Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public virtual Class Classes { get; set; }
public virtual Classroom Classrooms { get; set; }
}
public class Class
{
public Class()
{
this.Students = new HashSet<Student>();
this.ClassSchedules = new HashSet<ClassSchedule>();
}
public int Id { get; set; }
public int LevelId { get; set; }
[StringLength(50)]
public string ClassName { get; set; }
public int MaxStudents { get; set; }
public virtual Level Levels { get; set; }
public ICollection<Student> Students { get; set; }
public ICollection<ClassSchedule> ClassSchedules { get; set; }
}
public class Classroom
{
public Classroom()
{
this.ClassSchedules = new HashSet<ClassSchedule>();
}
public int Id { get; set; }
public string ClassRoomName { get; set; }
public int MaxStudents { get; set; }
public ICollection<ClassSchedule> ClassSchedules { get; set; }
}
And have got the following Code in my migration configuration file:
// all classrooms
context.Classrooms.Add(new Classroom() { ClassRoomName = "Cherry", MaxStudents = 20 });
context.Classrooms.Add(new Classroom() { ClassRoomName = "Pear", MaxStudents = 12 });
context.Classrooms.Add(new Classroom() { ClassRoomName = "Apple", MaxStudents = 12 });
// classes
context.Classes.Add(new Class() { ClassName = "YR1", MaxStudents = 12, LevelId = 1 });
context.Classes.Add(new Class() { ClassName = "YB1", MaxStudents = 6, LevelId = 3 });
context.Classes.Add(new Class() { ClassName = "IELTS L1", MaxStudents = 40, LevelId = 14 });
Now what i want to do is something like this:
// ClassRoom schedules, links the class and classroom on a certain time
context.ClassSchedules.Add(new ClassSchedule() { StartTime = DateTime.Parse("2017-09-17T18:00:00"), EndTime = DateTime.Parse("2017-09-17T20:00:00"), Classes = 1, Classrooms = 1 });
Another thing I tried was:
context.ClassSchedules.Add(new ClassSchedule() { StartTime = DateTime.Parse("2017-09-17T18:00:00"), EndTime = DateTime.Parse("2017-09-17T20:00:00"), Classes = { Id = 1 }, Classrooms = { Id = 2 } });
However that does't work, also note that I cannot use new Classes() { Id = 1 } as this will create a new entity.
Any help will be appreciated, Cheers!
You can add classrooms and classes to the collections in ClassSchedule:
var classRoom1 = new Classroom { ClassRoomName = "Cherry", MaxStudents = 20 };
var class1 = new Class { ClassName = "YR1", MaxStudents = 12, LevelId = 1 };
var classSchedule1 = new ClassSchedule { StartTime = ... };
classSchedule1.Classrooms.Add(classRoom1);
classSchedule1.Classess.Add(class1);
context.ClassSchedules.Add(classSchedule1);
etc.
That's the gist of it. Add classes and classrooms that belong to a schedule to the ClassSchedules collections and add the schedule to the context. Add classes and classrooms that don't belong to a schedule (yet) to the DbSets of the context.
I couldn't get it to work with the method #gertArnold suggested, but after looking around for some time I found that it is possible to do it via the AddOrUpdate method and it will also check whether the value already exists or not. In that case it will be written as follows:
context.ClassSchedules.AddOrUpdate(cs => cs.ScheduleName,
new ClassSchedule
{
ScheduleName = "Yippie red class",
StartTime = DateTime.Parse("2017-09-17T18:00:00"),
EndTime = DateTime.Parse("2017-09-17T20:00:00"),
Classes = new Class()
{
ClassName = "YR1",
MaxStudents = 12,
LevelId = 1
},
Classrooms = new Classroom()
{
ClassRoomName = "Cherry",
MaxStudents = 20
}
});
Hope it will help anyone.

How to use Include method in LINQ To Entity when not sure that it has the specified navigation property?

I give you a CF example:
public class MyContext : DbContext
{
public DbSet<A> A { get; set; }
}
public class A
{
public int E { set; get; }
}
public class B : A
{
public int F { set; get; }
}
public class C : A
{
public int G { set; get; }
public virtual D D { set; get; }
}
public class D { }
and the query is like this:
using (var context = new MyContext())
{
var queryResult = context.A.Include("D").Select(a => a);
}
and it throws an exception with this message:
A specified Include path is not valid. The EntityType 'A' does not
declare a navigation property with the name 'D'.
How would you solve this with only one LINQ To Entity query?
Here is a work around
using (var context = new MyContext())
{
var typeA=typeOf(A);
var queryResult ;
if( typeA.GetProperty("D")!=null)
queryResult = context.A.Include("D").Select(a => a);
}

TPC mappint generate the base class table

I want to achieve TPC mapping by code-first, and I have read this article:
Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)
I've wrote the code as below.
namespace TPCTest
{
class Program
{
static void Main(string[] args)
{
using (TestContext context = new TestContext())
{
Manager m = new Manager();
m.AnnualSalary = 100000;
m.Name = "Allen";
m.Sex = true;
m.Id = 1;
Worker w = new Worker();
w.Id = 2;
w.Name = "John";
w.Sex = true;
w.MonthlyPay = 5000;
context.empSet.Add(m);
context.empSet.Add(w);
context.SaveChanges();
}
}
}
abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public bool Sex { get; set; }
}
class Manager : Employee
{
public decimal AnnualSalary { get; set; }
public string Department { get; set; }
}
class Worker : Employee
{
public decimal MonthlyPay { get; set; }
}
class TestContext : DbContext
{
public DbSet<Employee> empSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Manager>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Manager");
});
modelBuilder.Entity<Worker>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Worker");
});
modelBuilder.Entity<Employee>().Property(e => e.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None);
}
}
}
But after running the code, I found the Employee table also exist in database, anyone can help?
Thanks in advance!
Use EF 5.0 or higher!
The issue seems to be fixed since that version. With EF 4.3.1 I could reproduce the behavior. So just update.