EF 6.1.3 how to seed on-to-many relationship - entity-framework

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.

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.

Is this the best way to add a many to many when you require the id that you don't have yet?

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace EventsCoreApi.Models.Entities
{
public partial class Event
{
public Event()
{
EventHasMusicGenre = new HashSet<EventHasMusicGenre>();
}
public uint EventId { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
[Timestamp]
public byte[] StartTime { get; set; }
[Timestamp]
public byte[] EndTime { get; set; }
public string Description { get; set; }
public string PolicyMinimumAge { get; set; }
public string PolicyDescription { get; set; }
public uint DressCodeId { get; set; }
public virtual DressCode DressCode { get; set; }
public virtual ICollection<EventHasMusicGenre> EventHasMusicGenre { get; set; }'
[HttpPost]
public async Task<ActionResult<Event>> PostEvent(EventDto eventFromClient)
{
var newEvent = new Event
{
Name = eventFromClient.Name,
StartDate = eventFromClient.StartDate,
EndDate = eventFromClient.EndDate,
StartTime = eventFromClient.StartTime,
EndTime = eventFromClient.StartTime,
Description = eventFromClient.Description,
DressCodeId = eventFromClient.DressCodeId,
PolicyMinimumAge = eventFromClient.PolicyMinimumAge,
PolicyDescription = eventFromClient.PolicyDescription
};
_context.Event.Add(newEvent);
await _context.SaveChangesAsync();
EventHasMusicGenre music1 = new EventHasMusicGenre()
{
EventId = newEvent.EventId,
MusicGenreId = eventFromClient.MusicGenres[0].MusicGenreId
};
EventHasMusicGenre music2 = new EventHasMusicGenre()
{
EventId = newEvent.EventId,
MusicGenreId = eventFromClient.MusicGenres[1].MusicGenreId
};
await _eventHasMusicGenresRepository.PostEventHasMusicGenre(music1);
await _eventHasMusicGenresRepository.PostEventHasMusicGenre(music2);
await _context.SaveChangesAsync();
I have to create an event object who has a many to many relationship so the linking table will be eventHasMusicGenre who has it s own repo and methods is this best way to do it?
this pattern is fine, however see below for alternative:
List<EventHasMusicGenre> genres = new List<EventHasMusicGenre>();//declare list
foreach(var g in eventFromClient.MusicGenres)//loop client array
{
genres.Add(new EventHasMusicGenre(){//add to list
MusicGenreId = g.MusicGenreId
})
}
var newEvent = new Event
{
Name = eventFromClient.Name,
StartDate = eventFromClient.StartDate,
EndDate = eventFromClient.EndDate,
StartTime = eventFromClient.StartTime,
EndTime = eventFromClient.StartTime,
Description = eventFromClient.Description,
DressCodeId = eventFromClient.DressCodeId,
PolicyMinimumAge = eventFromClient.PolicyMinimumAge,
PolicyDescription = eventFromClient.PolicyDescription,
MusicGenres : [ genres ] //list as part of model, no need to worry about the id
};
This way you dont have to hardcode indices

HttpClient.PostAsJsonAsync can`t Serialize inherited property

my code is
public class BaseDTO
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class DataDTO : BaseDTO
{
public int Level { get; set; }
public DateTime ChangedDate { get; set; }
}
I call web-api by httpclient
static void Main(string[] args)
{
var httpClientHandler = new HttpClientHandler();
httpClientHandler.UseDefaultCredentials = true;
var client = new HttpClient(httpClientHandler);
client.BaseAddress = new Uri("http://localhost/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var dto = new DataDTO()
{
Id = 1,
Code = "a",
Name = "A",
Level = 10,
ChangedDate = DateTime.Now
};
HttpResponseMessage resp =
client.PostAsJsonAsync(
"api/MyApi/Creat", dto).Result;
if (resp.IsSuccessStatusCode)
{
}
}
when i debug,i found the data that server received ,"Id","Code" and "Name" inherited from base class were all null,"Level" and "ChangedDate" were right.
I googled,but I cannot find my reason.
changed to use restsharp,it works well

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

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

How do I chain properties with Code First entity framework?

I'm trying to do what seems fairly simple but I'm getting a null reference....
I have a null on the assoc files property in the last statement...
TestInfo.AggregateRoutes.MainBlogEntry = new Blog { BlogType = 1, Title = TestInfo.UniqueRecordIdentifier, Description = TestInfo.UniqueRecordIdentifier, DateAdded = DateTime.Now, User = TestInfo.UniqueRecordIdentifier };
IBlogRepository blogRepo = new BlogRepository();
var assocFile = new AssocFile { Name = TestInfo.UniqueRecordIdentifier, Url = TestInfo.UniqueRecordIdentifier };
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles.Add(assocFile);
This is the code I have written to support what I'm trying to do...
public class PteDotNetContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<AssocFile> AssocFiles { get; set; }
}
public class Blog
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BlogId { get; set; }
public int BlogType { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public string User { get; set; }
public virtual ICollection<AssocFile> AssocFiles { get; set; }
}
public class AssocFile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AssocFileId { get; set; }
public int BlogId { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public virtual Category Category { get; set; }
}
I thought the whole point in declaring it virtual was that it would create a foreign key constraint?
When you instantiate an entity you also need to initialize the collection navigational properties before you access it for the first time. In your case MainBlogEntry.AssocFiles = new List<AssocFile>();. The reason for this is, your property implementation does not contain any logic to initialize the collection.
When EF creates new instances of your entities, it sub classes your entities (ie Proxy Creation) and over ride the default functionality of your properies.
TestInfo.AggregateRoutes.MainBlogEntry = new Blog { BlogType = 1, Title = TestInfo.UniqueRecordIdentifier, Description = TestInfo.UniqueRecordIdentifier, DateAdded = DateTime.Now, User = TestInfo.UniqueRecordIdentifier };
IBlogRepository blogRepo = new BlogRepository();
var assocFile = new AssocFile { Name = TestInfo.UniqueRecordIdentifier, Url = TestInfo.UniqueRecordIdentifier };
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles = new List<AssocFile>();
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles.Add(assocFile);