Seeding my EF database with data - entity-framework

I'm trying to populate my database using Entity Framework. I'm using the Seed override and using set initializer from my DbContext. When I create my database from scratch, it doesn't seem to add these values in!
public class PokemonDatabaseInitializer : CreateDatabaseIfNotExists<PkmnContext>
{
protected override void Seed(PkmnContext context)
{
var pkm = new Pokemon
{
Id = 25,
DisplayName = "Pikachu",
RegionId = 1
};
var region = new PokemonRegion
{
Id = 1,
Kanto = true,
};
var location = new PokemonLocation
{
AreaFound = "Viridian Forest",
Id = 1
};
context.Pokemons.Add(pkm);
context.PokemonRegions.Add(region);
context.PokemonLocations.Add(location);
context.SaveChanges();
base.Seed(context);
}
}
public class PkmnContext : DbContext
{
public PkmnContext()
{
Database.SetInitializer(new PokemonDatabaseInitializer());
}
public DbSet<Pokemon> Pokemons { get; set; }
public DbSet<PokemonRegion> PokemonRegions { get; set; }
public DbSet<PokemonLocation> PokemonLocations { get; set; }
}

you need to call
update-database
from package manager console. It also possible to call it from code after any migrations were applied
Please note that it will seed data each time you call it so i would add some checks if items exists already

Related

Duplicating entities from EF Core into MongoDB on Creation or Edit

I'm working on an asp.net core project that has both sql server and mongodb. the task is to add or update any entity that is being added or updated to sql server(using ef core), to mongo.
I figured I can override OnSaveChangesAsync but I have no idea how to access the entities that are being changed there in order to call the mongo service's update and insert methods on them.
This can be done by using interceptors:
The Model:
public class Student
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
DbContext:
public class StudentsContext : DbContext
{
public DbSet<Student> Students { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.AddInterceptors(new UpdateMongoInterceptor())
.UseSqlite(#"DataSource=C:\Databases\Students.db");
}
}
SaveChangesInterceptor:
public class UpdateMongoInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
UpdateMongoDb(eventData.Context);
return result;
}
private static void UpdateMongoDb(DbContext context)
{
context.ChangeTracker.DetectChanges();
foreach (var entry in context.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Added:
if (entry.Entity is Student student)
{
//Update MongoDB...
Console.WriteLine($"Push student entity to mongoDB Id: {student.Id}, Name:{student.Name}, Age:{student.Age}");
}
break;
}
}
}
Usege:
using (var context = new StudentsContext())
{
var student = new Student {Id = Guid.NewGuid(), Name = "Jon Snow", Age = 25};
context.Add(student);
context.SaveChanges();
}
Interceptors documentations can be found here: Example: SaveChanges interception for auditing
** If you are using DB generated keys you should override SavedChanges and figure out of the entity was updated or added.

Update Navigation Property with Entity.CurrentValues.SetValues

I have a Kalem Entity with a collection of DigerKalemMaliyetleri property, which is a collection of MaliyetBirimi objects. DigerKalemMaliyetleri is of JSON type and stored at the same table as a JSON column.
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
When I try to update entity with only DigerKalemMaliyetleri property changed:
DataContext.Entry<Kalem>(first).CurrentValues.SetValues(second);
SQL Update command isn't executed and database record is not updated.
How could I update the entity without explicitly setting DigerKalemMaliyetleri property?
Regards
I had the same problem, you cann't actually use SetValues to update navigation property, you nead instead use DataContext.Update(YourNewObj) and then DataContext.SaveChanges();, or if you want to use SetValues approach, you need:
-Get the exist entry
Kalem existObj = DataContext.Kalems.Find(YourNewObj.Id);
-Loop in navigations of updating entry and the existing one to set the values of updating entry:
foreach(var navObj in DataContext.Entry(YourNewObj).Navigations)
{
foreach(var navExist in DatatContext.Entry(existObj).Navigations)
{
if(navObj.Metadata.Name == navExist.MetaData.Name)
navExist.CurrentValue = navObj.CurrentValue;
}
}
-Update also changes of direct properties:
DataContext.Entry(existObj).CurrentValues.SetValues(YourNewObj);
-Save your Updating:
DataContext.SaveChanges();
You can also check if you need to load your Navigations before going in foreach loop, otherwise you will get an error.
Please if you see beter scenario, correct me.
It's hard to know exactly what you're doing without a complete code sample. Note also that you're trying to set all properties of first from second, including e.g. the Id, which is probably not what you want.
Here's a complete code sample which works for me:
await using (var ctx = new BlogContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Kalem.Add(new()
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 1, Maliyet = 2 }
}
});
await ctx.SaveChangesAsync();
}
await using (var ctx = new BlogContext())
{
var first = ctx.Kalem.Find(1);
var second = new Kalem
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 3, Maliyet = 4 }
}
};
ctx.Entry(first).Property(k => k.DigerKalemMaliyetleri).CurrentValue = second.DigerKalemMaliyetleri;
await ctx.SaveChangesAsync();
}
public class BlogContext : DbContext
{
public DbSet<Kalem> Kalem { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}

EfCore 3 and Owned Type in same table, How do you set owned instance

How do you set owned type instance with efcore3?
In following example an exception is raised
'The entity of type 'Owned' is sharing the table 'Principals' with
entities of type 'Principal', but there is no entity of this type with
the same key value that has been marked as 'Added'.
If I set Child property inline savechanges doesn't update child properties
I can't find any example about this. I tried with several efcore3 builds and daily builds. What didn't I understand?
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace TestEF
{
class Program
{
static void Main(string[] args)
{
var id = Guid.NewGuid();
using (var db = new Ctx())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var p = new Principal {Id = id};
db.Principals.Add(p);
db.SaveChanges();
}
using (var db = new Ctx())
{
var p = db.Principals.Single(o => o.Id == id);
p.Child = new Owned();
p.Child.Prop1 = "Test2";
p.Child.Prop2 = "Test2";
db.SaveChanges();
}
}
public class Principal
{
public Guid Id { get; set; }
public Owned Child { get; set; }
}
public class Owned
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
public class Ctx : DbContext
{
public DbSet<Principal> Principals { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=TestEF;Trusted_Connection=True;Persist Security Info=true");
}
protected override void OnModelCreating(ModelBuilder mb)
{
var emb = mb.Entity<Principal>();
emb
.OwnsOne(o => o.Child, cfg =>
{
cfg.Property(o => o.Prop1).HasMaxLength(30);
//cfg.WithOwner();
});
}
}
}
}
This is a bug, filed at https://github.com/aspnet/EntityFrameworkCore/issues/17422
As a workaround you could make the child appear as modified:
db.ChangeTracker.DetectChanges();
var childEntry = db.Entry(p.Child);
childEntry.State = EntityState.Modified;
db.SaveChanges();
Try this instead:
_context.Update(entity);
This will update all the owned properties so SaveChanges() updates them, too.

Prevent EF from saving full object graph

I have a model as below
public class Lesson
{
public int Id { get; set; }
public Section Div { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
}
I also have DB Context as below
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Section> Sections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Then I use the following code to call the database
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Entry(d.Div).State = EntityState.Detached;
c.Lessons.Add(d);
c.SaveChanges();
}
I am expecting this code to save just the Lesson object, not to save the full graph of Lesson and Section, but what happens is that it saves the full graph.
How do I prevent it from doing that?
When you add an entity to DbSet, entityframework will add all of its relative. You need to detach the entity you don't want to add, after adding parent entity to DbSet.
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
}
if you want to add section, related to the lesson , you need to use the same context, or create a new context and load the lesson.
you can use this code
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
//you can use this code
ed.Lesson = d;
// or this code
d.Div = ed;
c.Sections.Add(ed);
c.SaveChanges();
}

Seeding not working in Entity Framework Code First Approach

I am developing a .Net project. I am using entity framework code first approach to interact with database. I am seeding some mock data to my database during development. But seeding is not working. I followed this link - http://www.entityframeworktutorial.net/code-first/seed-database-in-code-first.aspx.
This is my ContextInitializer class
public class ContextInitializer : System.Data.Entity.CreateDatabaseIfNotExists<StoreContext>
{
protected override void Seed(StoreContext context)
{
IList<Brand> brands = new List<Brand>();
brands.Add(new Brand { Name = "Giordano" ,TotalSale = 1 });
brands.Add(new Brand { Name = "Nike" , TotalSale = 3 });
foreach(Brand brand in brands)
{
context.Brands.Add(brand);
}
base.Seed(context);
context.SaveChanges();
}
}
This is my context class
public class StoreContext : DbContext,IDisposable
{
public StoreContext():base("DefaultConnection")
{
Database.SetInitializer(new ContextInitializer());
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<Brand> Brands { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
This is my brand class
public class Brand
{
public int Id { get; set; }
[Required]
[MaxLength(40)]
public string Name { get; set; }
public int TotalSale { get; set; }
}
I searched solutions online and I followed instructions. I run context.SaveChanges as well. But it is not seeding data to database. Why it is not working?
You are taking the wrong initializer, CreateDatabaseIfNotExists is called only if the database not exists!
You can use for example DropCreateDatabaseIfModelChanges:
Solution 1)
public class ContextInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<StoreContext>
{
You have to take care with this approach, it !!!removes!!! all existing data.
Solution 2)
Create a custom DbMigrationsConfiguration:
public class Configuration : DbMigrationsConfiguration<StoreContext>
{
public Configuration()
{
// Take here! read about this property!
this.AutomaticMigrationDataLossAllowed = true;
this.AutomaticMigrationsEnabled = false;
}
protected override void Seed(StoreContext context)
{
IList<Brand> brands = new List<Brand>();
brands.Add(new Brand { Name = "Giordano", TotalSale = 1 });
brands.Add(new Brand { Name = "Nike", TotalSale = 3 });
foreach (Brand brand in brands)
{
context.Brands.AddOrUpdate(m => m.Name, brand);
}
base.Seed(context);
context.SaveChanges();
}
}
In this way you can called( !!Before you create the DbContext or in the DbContext constructor!!):
// You can put me also in DbContext constuctor
Database.SetInitializer(new MigrateDatabaseToLatestVersion<StoreContext , Yournamespace.Migrations.Configuration>("DefaultConnection"));
Notes:
DbMigrationsConfiguration need to know about the connection string you can provide this info in the constructor or from outside.
In Your DbMigrationsConfiguration you can configure also:
MigrationsNamespace
MigrationsAssembly
MigrationsDirectory
TargetDatabase
If you leave everything default as in my example then you do not have to change anything!
Setting the Initializer for a Database has to happen BEFORE the context is ever created so...
public StoreContext():base("DefaultConnection")
{
Database.SetInitializer(new ContextInitializer());
}
is much to late. If you made it static, then it could work:
static StoreContext()
{
Database.SetInitializer(new ContextInitializer());
}
Your code is working if you delete your existing database and the EF will create and seeding the data
Or
You can use DbMigrationsConfiguration insted of CreateDatabaseIfNotExists and change your code as follow:
First you have to delete the existing database
ContextInitializer class
public class ContextInitializer : System.Data.Entity.Migrations.DbMigrationsConfiguration<StoreContext>
{
public ContextInitializer()
{
this.AutomaticMigrationDataLossAllowed = true;
this.AutomaticMigrationsEnabled = true;
}
protected override void Seed(StoreContext context)
{
IList<Brand> brands = new List<Brand>();
brands.Add(new Brand { Name = "Giordano", TotalSale = 1 });
brands.Add(new Brand { Name = "Nike", TotalSale = 3 });
foreach (Brand brand in brands)
{
context.Brands.AddOrUpdate(m => m.Name, brand);
}
base.Seed(context);
context.SaveChanges();
}
}
StoreContext
public class StoreContext : DbContext, IDisposable
{
public StoreContext() : base("DefaultConnection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<StoreContext, ContextInitializer>());
// Database.SetInitializer(new ContextInitializer());
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<Brand> Brands { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Then any change in your seed will automatically reflected to your database