I'm having some trouble getting the Seed method of EF to run. I've run update-database in the PMC - but no effect on the DB. Here's the method:
public class PhilosopherInitialiser : System.Data.Entity.DropCreateDatabaseIfModelChanges<PhilosopherContext>
{
protected override void Seed(PhilosopherContext context)
{
var philosophers = new List<Philosopher>{
new Philosopher {
FirstName = "Bertrand",
LastName = "Russell",
DateOfBirth = DateTime.Parse("1872-05-18"),
DateOfDeath = DateTime.Parse("1970-02-02"),
IsAlive = false,
NationalityID = 1,
AreaID = 7,
Description = "Here's some text about Bertrand Russell"
},
new Philosopher {
FirstName = "Immanuel",
LastName = "Kant",
DateOfBirth = DateTime.Parse("1724-04-22"),
DateOfDeath = DateTime.Parse("1804-02-12"),
IsAlive = false,
NationalityID = 3,
AreaID = 1,
Description = "Here's some text about Immanuel Kant"
},
new Philosopher {
FirstName = "John",
LastName = "Rawls",
DateOfBirth = DateTime.Parse("1921-02-21"),
DateOfDeath = DateTime.Parse("2002-11-24"),
IsAlive = false,
NationalityID = 9,
AreaID = 3,
Description = "Here's some text about John Rawls"
}
};
philosophers.ForEach(p => context.Philosophers.Add(p));
context.SaveChanges();
var nationalities = new List<Nationality>
{
new Nationality { Name = "English" },
new Nationality { Name = "Scotish" },
new Nationality { Name = "German" },
new Nationality { Name = "French" },
new Nationality { Name = "Greek" },
new Nationality { Name = "Italian" },
new Nationality { Name = "Spanish" },
new Nationality { Name = "Russian" },
new Nationality { Name = "American" }
};
nationalities.ForEach(n => context.Nationalities.Add(n));
context.SaveChanges();
var areas = new List<Area>{
new Area { Name = "Metaphysics" },
new Area { Name = "Existentialism" },
new Area { Name = "Political philosophy" },
new Area { Name = "Philosophy of the mind" },
new Area { Name = "Aesthetics" },
new Area { Name = "Social philosophy" },
new Area { Name = "Logic" },
new Area { Name = "Moral philosophy" },
new Area { Name = "Epistemology" }
};
areas.ForEach(a => context.Areas.Add(a));
context.SaveChanges();
var books = new List<Book>
{
new Book {
Title = "The impact of science on society",
PhilosopherID = 1,
AreaID = 6
},
new Book {
Title = "The analysis of mind",
PhilosopherID = 1,
AreaID = 4
},
new Book {
Title = "Marriage and morals",
PhilosopherID = 1,
AreaID = 8
},
new Book{
Title = "Critique of pure reason",
PhilosopherID = 2,
AreaID = 9
},
new Book{
Title = "The metaphysics of morals",
PhilosopherID = 2,
AreaID = 8
},
new Book{
Title = "A theory of justice",
PhilosopherID = 3,
AreaID = 3
}
};
books.ForEach(b => context.Books.Add(b));
context.SaveChanges();
}
}
}
And here's my PhilosopherContext class:
public class PhilosopherContext : DbContext
{
public PhilosopherContext() : base("PhilosopherContext")
{
}
public DbSet<Philosopher> Philosophers { get; set; }
public DbSet<Area> Areas { get; set; }
public DbSet<Nationality> Nationalities { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>()
.HasRequired(p => p.Philosopher)
.WithMany()
.HasForeignKey(p => p.PhilosopherID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Philosopher>()
.Property(p => p.DateOfBirth)
.HasColumnType("datetime2");
modelBuilder.Entity<Philosopher>()
.Property(p => p.DateOfDeath)
.HasColumnType("datetime2");
}
}
}
Inside the Web.Config file I'm using initialising the DB here:
<contexts>
<context type="PhilosophersLibrary.DAL.PhilosopherContext, PhilosophersLibrary">
<databaseInitializer type="PhilosophersLibrary.DAL.PhilosopherInitialiser, PhilosophersLibrary" />
</context>
</contexts>
Does anyone have any suggestions? I feel that the method might not be called.
UPDATE
I seem to be making progress. The Areas and Nationalities tables are being seeded with the data. But I have to comment out the Philosophers data and the Books data. Is there something wrong with my data model?
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
[Display(Name = "Philosopher")]
public int PhilosopherID { get; set; }
[Display(Name = "Area")]
public int AreaID { get; set; }
public virtual Philosopher Philosopher { get; set; }
public virtual Area Area { get; set; }
}
public class Philosopher
{
// <className>ID pattern causes property to be primary key
public int PhilosopherID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Display(Name = "Date of birth")]
public DateTime DateOfBirth { get; set; }
[Display(Name = "Date of death")]
public DateTime DateOfDeath { get; set; }
public Boolean IsAlive { get; set; }
public string Description { get; set; }
// Foreign keys have corresponding navigation properties
// <NavigationProperty>ID naming convention cause EF to identify foreign keys
public int NationalityID { get; set; }
public int AreaID { get; set; }
// Navigation properties - defined as virtual to use LazyLoading
// Nationality and Area have a 1 to 1 relationship with philosopher
// Books has a 1 to many relationship with philosopher
public virtual Nationality Nationality { get; set; }
public virtual Area Area { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
Try to use this:
CreateDatabaseIfNotExists<PhilosopherContext>
instead of this:
DropCreateDatabaseIfModelChanges<PhilosopherContext>
Also I would add:
public PhilosopherContext() : base("PhilosopherContext")
{
Database.SetInitializer<PhilosopherContext>(new CreateDatabaseIfNotExists<PhilosopherContext>());
}
Related
I am trying to create a seed for my database in ASP.NET Core but I am having trouble with the relationships between the models. I have 3 models with 2 relationships. I have the following models:
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public Guid ID { get; set; } = Guid.NewGuid();
public Course Course { get; set; }
public Student Student { get; set; }
public Grade Grade { get; set; }
}
public class Course
{
public Guid ID { get; set; } = Guid.NewGuid();
public string Title { get; set; }
public int Credits { get; set; }
public List<Enrollment>? Enrollments { get; set; }
}
public class Student
{
public Guid ID { get; set; } = Guid.NewGuid();
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; } = DateTime.Now;
public List<Enrollment>? Enrollments { get; set; }
}
On my DBContext I try to create the seed:
List<Student> students = new List<Student>()
{
new Student {FirstName = "Jaimie", LastName = "Vos", EnrollmentDate = DateTime.Now },
new Student {FirstName = "Bas", LastName = "Milius", EnrollmentDate = DateTime.Now },
new Student {FirstName = "Rien", LastName = "Bijl", EnrollmentDate = DateTime.Now },
new Student {FirstName = "Rajeck", LastName = "Massa", EnrollmentDate = DateTime.Now }
};
modelBuilder.Entity<Student>().HasData(students);
List<Course> courses = new List<Course>()
{
new Course {Title = "Wiskunde", Credits = 20},
new Course {Title = "Nederlands", Credits = 15},
new Course {Title = "Frans", Credits = 10},
};
modelBuilder.Entity<Course>().HasData(courses);
Enrollment test = new Enrollment();
test.Grade = Grade.A;
test.Course = courses[0];
test.Student = students[1];
modelBuilder.Entity<Enrollment>().HasData(test);
But when I run this I get the error:
The seed entity for entity type 'Enrollment' cannot be added because no value was provided for the required property 'CourseID'.
I followed the documentation for relations, does someone know a way to fix this issue?
I've found that you can just create a new object (not specifying the type) and give it a property specifying the related ID. So you should be able to do something like the following:
modelBuilder.Entity<Course>().HasData(new [] {
new { Title = "Frans", Credits = 10, Id = <courseGUID1> },
new { Title = "Nederlands", Credits = 15, Id = <courseGUID2> }
});
modelBuilder.Entity<Enrollment>().HasData(new [] {
new { Grade = Grade.A, CourseId = <courseGUID1>, StudentId = <studentGUID1> }
});
In conclusion, I hate seed data, but have used it a fair amount and the above solution seems to get me by. It's not intuitive and would be nice if the code you had worked!
I would assume that the IDs should be generated when created, but obviously that doesn't work.
PS: If using auto-incrementing integer IDs, I usually just use negative integers to avoid conflict with the generated IDs.
I have 2 models:
public class Office
{
[Key] public int OfficeId { get; set; }
public string Brand { get; set; }
public string Type { get; set; }
[NotMapped] public IRepository<CarItem> CarsDataBase { get; set; }
public virtual ICollection<CarItem> Cars { get; set; }
public Office(string brand, string type)
{
Type = type;
Cars = new List<CarItem>();
Brand = brand;
}
}
and
public class CarItem
{
public CarItem() { } //for serialization
public CarItem(string brand, string model, uint price, uint stockBalance)
{
Brand = brand;
Model = model;
Price = price;
StockBalance = stockBalance;
}
[Key] public int ItemId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public uint Price { get; set; }
public uint StockBalance { get; set; }
public int? OfficeId { get; set; }
public Office Office { get; set; }
}
and DataBase Context
public class EFDataBaseContext : DbContext
{
public DbSet<Office> Offices => Set<Office>();
public DbSet<CarItem> CarItems => Set<CarItem>();
public EFDataBaseContext()
{
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=carstoredb;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Office>().HasData
(
new Office("Audi", "SQL")
{
OfficeId = 1,
},
new Office("Scoda", "SQL")
{
OfficeId = 2,
}
);
modelBuilder.Entity<CarItem>(entity =>
{
entity.HasOne(item => item.Office).WithMany(office => office.Cars).HasForeignKey(item => item.OfficeId).OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<SparePart>(entity =>
{
entity.HasOne(item => item.Office).WithMany(office => office.Parts).HasForeignKey(item => item.OfficeId).OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<CarItem>().HasData
(
new CarItem { OfficeId = 1, ItemId = 1, Brand = "Audi", Model = "A228", Price = 4, StockBalance = 14 },
new CarItem { OfficeId = 1, ItemId = 2, Brand = "Audi", Model = "Super", Price = 44, StockBalance = 5 },
new CarItem { OfficeId = 2, ItemId = 3, Brand = "Scoda", Model = "Logan", Price = 47, StockBalance = 9 },
new CarItem { OfficeId = 2, ItemId = 4, Brand = "Scoda", Model = "Spider", Price = 78, StockBalance = 3 }
);
}
Manually I added an office called BSU.
And in main function I write this:
using (EFDataBaseContext db = new EFDataBaseContext())
{
Office office = mainOffice.Dealerships.FirstOrDefault(of => of.Brand == "BSU");
CarItem carItem = new CarItem(office.Brand, "BSss", 2222, 4);
office.CarsDataBase ??= new EFDataBase(office);
office.CarsDataBase.Create(carItem);
}
Adding a new CarItem to the BSU somehow magically creates a new office named BSU in my database every time a new CarItem is added to the BSU.
using (EFDataBaseContext db = new EFDataBaseContext())
{
Office office = mainOffice.Dealerships.FirstOrDefault(of => of.Brand == "Audi");
CarItem carItem = new CarItem(office.Brand, "AuuuU", 2222, 4);
office.CarsDataBase ??= new EFDataBase(office);
office.CarsDataBase.Create(carItem);
}
Adding a new CarItem to an Audi, on the other hand, does absolutely nothing. No new cars with the Audi brand appear in the database, and nothing at all.
Seems like you're over complicating things
Adding a new Car to an existing Office should perhaps looks like:
var ctx = new EFDatabaseContext();
var off = ctx.Offices.FirstOrDefault(o => o.Type == "Main" and o.Brand == "Audi");
off.Cars.Add(new CarItem(off.Brand, "AuuuU", 2222, 4));
ctx.SaveChanges();
Find the office, add a car, save the changes
I have two related classes as follows:
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public long FavoriteCourseId { get; set; }
}
public class Course
{
public long Id { get; set; }
public string Name { get; set; }
}
There is one to one relationship between the two. Student has a foreign key which references the Course via the FavoriteCourseId.
My fluent mappings are as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Student>(entity => {
entity.ToTable("Student").HasKey(k => k.Id);
entity.Property(p => p.Id).HasColumnName("StudentID");
entity.Property(p => p.Name).IsRequired();
entity.Property(p => p.Email).IsRequired();
entity.Property(p => p.FavoriteCourseId).IsRequired();
});
modelBuilder.Entity<Course>(entity => {
entity.ToTable("Course").HasKey(k => k.Id);
entity.Property(p => p.Id).HasColumnName("CourseID");
entity.Property(p => p.Name).IsRequired();
});
modelBuilder.Entity<Student>()
.HasOne(typeof(Course))
.WithOne()
.HasForeignKey("Student", "FavoriteCourseId")
.HasConstraintName("FK_Student_Course");
modelBuilder.Entity<Course>().HasData(
new Course { Id = 1, Name = "Calculus" },
new Course { Id = 2, Name = "Chemistry" },
new Course { Id = 3, Name = "Literature" },
new Course { Id = 4, Name = "Trigonometry" },
new Course { Id = 5, Name = "Microeconomics" });
modelBuilder.Entity<Student>().HasData(
new Student { Id = 1, Name = "Alice", Email = "alice#gmail.com", FavoriteCourseId = 2 },
new Student { Id = 2, Name = "Bob", Email = "bob#outlook.com", FavoriteCourseId = 2 });
}
Here, I am seeding two Students - Alice and Bob. However, when I create the init migration then the migration seeds only Bob and ignores Alice.
migrationBuilder.InsertData(
table: "Student",
columns: new[] { "StudentID", "Email", "FavoriteCourseId", "Name" },
values: new object[] { 2L, "bob#outlook.com", 2L, "Bob" });
Why is the data row for Alice being ignored? I also noticed that the data row only gets ignored when FavoriteCourseId is same for both Alice and Bob. If I change the FavoriteCourseId to different values for both rows then Alice gets inserted.
It seeds only one student because according your model the same course can only have one student. Change the model to this:
public class Student
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public long FavoriteCourseId { get; set; }
[ForeignKey(nameof(FavoriteCourseId ))]
[InverseProperty("FavoriteCourses")]
public virtual Course FavoriteCourse { get; set; }
}
public class Course
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(Student.FavoriteCourse))]
public virtual ICollection<Student> Students{ get; set; }
}
and the dbcontext:
modelBuilder.Entity<Student>(entity =>
{
entity.HasOne(d => d.FavoriteCourse)
.WithMany(p => p.Courses)
.HasForeignKey(d => d.FavoriteCourseId)
.OnDelete(DeleteBehavior.ClientSetNull);
}
modelBuilder.Entity<Course>().HasData(
new Course { Id = 1, Name = "Calculus" },
new Course { Id = 2, Name = "Chemistry" },
new Course { Id = 3, Name = "Literature" },
new Course { Id = 4, Name = "Trigonometry" },
new Course { Id = 5, Name = "Microeconomics" });
modelBuilder.Entity<Student>().HasData(
new Student { Id = 1, Name = "Alice", Email = "alice#gmail.com", FavoriteCourseId = 2 },
new Student { Id = 2, Name = "Bob", Email = "bob#outlook.com", FavoriteCourseId = 2 });
I have created a model for UnitOfMeasure (UOM) and a model for ingredient where I would like to use UOM to enter a default UOM for the ingredient.
public class IngredientModel
{
public int Id { get; set; }
public string Name { get; set; }
public UnitOfMeasureModel DefaultUOM { get; set; }
}
public class UnitOfMeasureModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
I would like to use the Name property in the IngredientModel.
In configure.cs I have put this code to create some default data for the database:
protected override void Seed(RecipeApplication.Models.RecipeApplicationDb context)
{
if (!context.UnitOfMeasures.Any())
{
context.UnitOfMeasures.AddOrUpdate(
u => u.Id,
new UnitOfMeasureModel { Name = "Gram", Abbreviation = "g" },
new UnitOfMeasureModel { Name = "Kilogram", Abbreviation = "kg"},
new UnitOfMeasureModel { Name = "Milligram", Abbreviation = "mg" }
);
}
if (!context.Ingredients.Any())
{
context.Ingredients.AddOrUpdate(
i => i.Id,
new IngredientModel { Name = "Italiaanse Ham", DefaultUOM =
);
}
}
I did not enter anything yet at default UOM because that is where I got stuck.
Could someone help me with this issue?
I'm assuming you just want to be able to access one of the UnitOfMeasureModel classes in both the UnitOfMeasures.AddOrUpdate and the UnitOfMeasures.AddOrUpdate methods. To do this create the instance before the calls and use that same instance in each AddOrUpdate method like so.....
protected override void Seed(RecipeApplication.Models.RecipeApplicationDb context)
{
var defaultUOM = new UnitOfMeasureModel { Name = "Gram", Abbreviation = "g" };
if (!context.UnitOfMeasures.Any())
{
context.UnitOfMeasures.AddOrUpdate(
u => u.Id,
defaultUOM,
new UnitOfMeasureModel { Name = "Kilogram", Abbreviation = "kg"},
new UnitOfMeasureModel { Name = "Milligram", Abbreviation = "mg" }
);
}
if (!context.Ingredients.Any())
{
context.Ingredients.AddOrUpdate(
i => i.Id,
new IngredientModel { Name = "Italiaanse Ham", DefaultUOM = defaultUOM
);
}
}
obviously you can change if gram is not the correct default
i have a List< PaperAbstract > class. a PaperAbstract class has a set of Authors. one of the Authors has a flag IsSubmitting true. how can i order my List< PaperAbstract > by the submitting authors LastName?
public class PaperAbstract
{
public string Title { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public bool IsSubmitting { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
for example:
var paperAbstracts = new List<PaperAbstract>();
paperAbstracts.Add(new PaperAbstract
{
Title = "Abstract 2",
Authors = new List<Author>
{
new Author { IsSubmitting = false, FirstName = "F5", LastName = "L5"},
new Author { IsSubmitting = true, FirstName = "F6", LastName = "L6"}
}
});
paperAbstracts.Add(new PaperAbstract
{
Title = "Abstract 3",
Authors = new List<Author>
{
new Author { IsSubmitting = true, FirstName = "F1", LastName = "L1"},
new Author { IsSubmitting = false, FirstName = "F2", LastName = "L2"}
}
});
paperAbstracts.Add(new PaperAbstract
{
Title = "Abstract 1",
Authors = new List<Author>
{
new Author { IsSubmitting = false, FirstName = "F3", LastName = "L3"},
new Author { IsSubmitting = true, FirstName = "F4", LastName = "L4"}
}
});
the correct order of paperAbstracts should be Abstract 3, Abstract 1, Abstract 2.
You can use OrderBy from LINQ:
var result = input.OrderBy(x => x.Authors
.First(a => a.IsSubmitting).LastName)