How to Seed in Entity Framework Core 2

Entity Framework CORE Seeding Joining table

There is not much to say about the concrete issue (which btw is not specific to joining entity, but any entity model seeding):

I am seed FileTypeId but not sure why EF core migration throwing error...

as the cause of the issue is included at the beginning of the error message:

because it has the navigation 'FileType' set.

And your entity has

public FileType FileType { get; set; } = new FileType();
// ^ ^ ^
// the problem

and the same for

public JobProfile JobProfile { get; set; } = new JobProfile();

which will be the next error if you resolve the original.

Remove both navigation property initializers (= new ...) and the problem will be gone.

As a general rule, you should never initialize reference navigation properties because it causes many side effects and/or improper behaviors (not only for seeding, but also eager/lazy/explicit data loading). Initializing collection navigation properties is arbitrary, but ok. Only reference navigation property initialization must be avoided. For more info, see EF codefirst : Should I initialize navigation properties? - quite old EF topic, but still applies.

If you are trying to resolve NRT warnings (as I guess), initializing with new is definitely not a proper way. One reason I don't like NRT is because it is forcing people to use "workarounds" for preventing compiler warnings, which in fact break the primary functionality. Specifically in EF Core, enabling NRT also changes the optional/required attribute of some properties, hence database column types (most noticeable for string properties/columns and reference navigations). You could read more about this in the Working with Nullable Reference Types topic in the official EF Core documentation, but in general I would just disable NRT for EF entity model classes.

How to seed an Admin user in EF Core 2.1.0?

As user cannot be seeded in a normal way in Identity just like other tables are seeded using .HasData() of .NET Core 2.1.

Microsoft Recommendation: For data that requires calls to external API, such as ASP.NET Core Identity users creation it is recommended to use custom initialization logic.

Seed Roles in .NET Core 2.1 using code given below in ApplicationDbContext Class :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);

modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "Admin", NormalizedName = "Admin".ToUpper() });
}

Seed Users With Roles by Following the steps given below.

Step 1: New class creation

public static class ApplicationDbInitializer
{
public static void SeedUsers(UserManager<IdentityUser> userManager)
{
if (userManager.FindByEmailAsync("abc@xyz.com").Result==null)
{
IdentityUser user = new IdentityUser
{
UserName = "abc@xyz.com",
Email = "abc@xyz.com"
};

IdentityResult result = userManager.CreateAsync(user, "PasswordHere").Result;

if (result.Succeeded)
{
userManager.AddToRoleAsync(user, "Admin").Wait();
}
}
}
}

Step 2: Now Modify ConfigureServices method in Startup.cs class.

Before Modification:

services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();

After Modification:

services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();

Step 3: Modify parameters of Configure Method in Startup.cs class.

Before Modification :

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//..........
}

After modification :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<IdentityUser> userManager)
{
//..........
}

Step 4 : Calling method of our Seed (ApplicationDbInitializer) class:

ApplicationDbInitializer.SeedUsers(userManager);

Note: You can also Seed Roles just like users by Injecting the RoleManager along with UserManager.

How to Seed self referencing tree using Entity Framework Core 2.2

The exception message is telling you that you cannot use navigation properties when seeding with HasData method, but instead you could specify the relationships only via FK properties.

In other words, you can't use SubGenres and ParentGenre navigation properties for specifying the relations, they can be specified only via ParentId property.

So remove the

SubGenres = DefaultSubGenresFactories.Action

line, and either consolidate the DefaultSubGenresFactories.Action and DefaultGenresFactories.Action to a single list of Genre and use that list in HasData call, or if you want to keep the DefaultGenresFactories and DefaultSubGenresFactories classes separate as they are currently, simply call HasData for both (it's additive):

modelBuilder.Entity<Genre>().HasData(DefaultGenresFactories.Action);
modelBuilder.Entity<Genre>().HasData(DefaultSubGenresFactories.Action);

How to properly write seed method in Entity Framework Core?

Maybe it's because people only read the title, but everybody jumped to how to seed in Entity Framework, period. Next, some came up with AddOrUpdate which doesn't exist in EF core.

Seeding in EF core has changed dramatically compared to EF6. In EF6's Seed method it was possible to save an object graph, i.e. objects containing references and collections. However, the AddOrUpdate method had a couple of issues (I won't spell them out here) that made its behavior very hard to follow or even get right if you weren't aware of them.

As an (over)reaction to that, the EF team decided to no longer let EF determine whether an entity should be added or updated. Seeding should either add an entity if it's non-existing, or do nothing otherwise. Never update. These considerations lead to a greatly (over)simplified seeding mechanism in EF core:

  • Entities should be identified by their hard-coded primary keys
    . If necessary, they're inserted with IDENTITY INSERT ON (in Sql Server).
  • Foreign keys should also be hard coded.
  • Navigation properties can't be populated. If they are, EF will throw an exception like

    InvalidOperationException: The seed entity for entity type 'Studio' cannot be added because it has the navigation 'Games' set. To seed relationships you need to add the related entity seed to 'Game' and specify the foreign key values {'StudioId'}.

That means that you have to add StudioId to Game. And StudioId and GenreId to Person. Independent associations (a Studio reference without StudioId) aren't supported. (I think this is a far-reaching architectural decision).

Doing that, your seeding code, simplified a bit, could look like:

var games = new[]
{
new Game{ Id = 1, Name = "Game1", StudioId = 1 },
new Game{ Id = 2, Name = "Game2", StudioId = 1 },
};
var studio = new Studio
{
Id = 1,
Name = "Studio1",
};

modelBuilder.Entity<Studio>().HasData(studio);
modelBuilder.Entity<Game>().HasData(games);

The circular reference StudioGame doesn't matter here because it represents only one foreign key.

However, circular references over two foreign keys are impossible. Suppose Studio had a Director property of type Person referred to by DirectorId and the director is also an employee:

var director = new Person { Id = 1, Name = "Director1", StudioId = 1 }; // Employee of Studio1

var studio = new Studio
{
Id = 1,
Name = "Studio1",
DirectorId = 1 // Director is "Director1"
};

Now there's a chicken-and-egg problem: both entities can only be inserted if the other one is inserted first.

InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved.

I think that's another far-reaching consequence of this design decision. In EF6, even with its crippled AddOrUpdate, at least it was possible to call SaveChanges twice to accommodate this scenario.

Considering that on top of all this, seeding isn't migration-friendly my stance on data seeding is: either don't support it (I wouldn't mind) or support it well.

How to seed entity framework core data with related data

About your questions:

  1. You just provide Id since you are seeding initial data
  2. You have to insert Stores, there is no such thing as "cascade insert", what would EF insert as Name in automatically generated Stores?

So you need also to seed your Stores like you are seeding Items:

modelBuilder.Entity<Store>().HasData(new Store {<properties>});

What would I also do, is to change you models like this:

public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

public int StoreId { get; set; } //or LocationId if you prefer

[Required]
public Store Location { get; set; }
}

public class Store
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

[Required]
public string Name { get; set; }

public virtual ICollection<Item> Items { get; set; }
}

and you need to set this as foreign key for your Location object:

modelBuilder.Entity<Item>().HasOne(e => e.Location)
.WithMany(e => e.Items)
.HasForeignKey(e => e.StoreId)
.OnDelete(DeleteBehavior.Restrict);

after that you can just adjust your seed method to:

modelBuilder.Entity<Item>().HasData(new Item { Id = 1, StoreId = 1 });

Note that there is no need to instantiate Location object.



Related Topics



Leave a reply



Submit