Seed data in EF 2.1 with related entities

1.6k Views Asked by At

Using ASP.NET Core 2.1 and Entity Framework Core 2.1 I have the following entities:

public class Category {
  public Int32 Id { get; set; }
  public String Name { get; set; }
}

public class Post {
  public Int32 Id { get; set; }
  public Int32 CategoryId { get; set; }
  public String Title { get; set; }
  public String Content { get; set; }
  public virtual Category Category { get; set; }
}

For seeding Categories I have the following:

modelBuilder.Entity<Category>().HasData(
  new { Id = 1, Name = "Reading" },
  new { Id = 2, Name = "Travelling" }
);

However, for seeding posts I am getting the data from YML files:

var posts = _service.GetPostsFromYMLFiles(path);

modelBuilder.Entity<Post>().HasData(posts);

The posts are taken from YML files and are the following:

new { CategoryName = "Reading", Title = "A", Content = "A Content" },
new { CategoryName = "Travelling", Title = "B", Content = "B Content" }

These YML files are created by a third party and I cannot change them.

To seed these posts I believe I need to:

  1. Get the correspondent Category Id for each CategoryName;
  2. Generate an ID for each post.

The step (2) seems easy but how can I accomplish step (1)?

2

There are 2 best solutions below

3
On BEST ANSWER

Instead of seeding the categories in place, save them to a variable first:

var categories = new[] 
{
    new Category { Id = 1, Name = "Reading" },
    new Category { Id = 2, Name = "Traveling" }
};

Then, pass this to HasData:

modelBuilder.Entity<Category>().HasData(categories);

With that, you can now query this list in memory to get the relevant categories while mapping your YML data to your Post entities:

var posts = _service.GetPostsFromYMLFiles(path);
modelBuilder.Entity<Post>().HasData(posts.Select((p, i) => new Post
{
    Id = i,
    Title = p.Title,
    Content = p.Content,
    CategoryId = categories.Single(c => c.Name == p.CategoryName).Id
});

CategoryId is non-nullable, so you probably want to create a fallback for if a particular category named in your YML does not actually exist. You may even consider seeding the categories themselves from the post YML, to ensure that everything in there actually exists:

var categories = posts.Select(p => p.CategoryName).Distinct().Select((c, i) => new Category
{
    Id = i,
    Name = c
}).ToArray();
0
On

To seed these posts I believe I need to:

Get the correspondent Category Id for each CategoryName;

Generate an ID for each post.

Yes! You are correct! you have to use CategoryId instead of CategoryName in the Post because CategoryId is the relational Key, not CategoryName. So your YML content should be like as follows:

new {Id = 1, CategoryId = 1, Title = "A", Content = "A Content" },
new {Id = 2, CategoryId = 2, Title = "B", Content = "B Content" }

Then in the OnModelCreating of the DbConext as follows:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      base.OnModelCreating(modelBuilder);
      
      var posts = _service.GetPostsFromYMLFiles(path);

      modelBuilder.Entity<Category>().HasData(
          new { Id = 1, Name = "Reading"},
          new { Id = 2, Name = "Travelling" }
      );

      modelBuilder.Entity<Post>().HasData(posts);        
}

These YML files are created by a third party and I cannot change them.

Then you have to write a custom service method where you will check every post in YMl file with CategoryName then you have to make new list of Post where you will replace CategoryName with CateogryId and its appropriate value as follows:

public class YmlPost
{
    public string CategoryName { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}


public class SeedDataService
{
    static List<Category> categoryList = new List<Category>()
    {
        new Category { Id = 1, Name = "Reading" },
        new Category { Id = 2, Name = "Traveling" }
    };

    public static List<Category> GetCategoriesForSeeding()
    {
       return categoryList;
    }
    public static List<Post> GetPostsForSeeding()
    {
        List<YmlPost> postListFromYML = _service.GetPostsFromYMLFiles(path);

        List<Post> posts = postListFromYML.Select((p, i) => new Post
            { 
              Id = i+1, // index is 0 based that's why you have to add 1.
              Title = p.Title,
              Content = p.Content,
              CategoryId = categoryList.Single(c => c.Name == 
                           p.CategoryName).Id
            }).ToList();
        return posts;
    }
}

Then in the OnModelCreating of the DbConext as follows:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      base.OnModelCreating(modelBuilder);
      
      var categories = SeedDataService.GetCategoriesForSeeding();
      modelBuilder.Entity<Category>().HasData(categories);
      
      var posts = SeedDataService.GetPostsForSeeding();
      modelBuilder.Entity<Post>().HasData(posts);
           
}