Seed Data without entity (join table)

Laurent Guigon 281 Reputation points
2023-09-02T12:44:51.36+00:00

Hello,
I've this configuration in my DBContext class :

            modelBuilder.Entity<Topic>()
                .HasMany(t => t.Keywords)
                .WithMany(k => k.Topics)
                .UsingEntity(j => j.ToTable("TopicKeyword"));

I would like to seed data for this association table, but it seems it's not possible. Since it's juste an assiciation table, I don't want to create a useless entity. However, to create a realistic data sample, I need to set up somme associations between my Topics and my Keywords...
And don't know how to do it without creating an entity, I'm not even sure if it's possible.

Here's my seed method:

        public static void SeedDummyData(this ModelBuilder builder, ActualDataModel actualData)
        {
            var dummyData = new DummyDataGeneratorService(actualData).BuildDataSample(100);
            builder.Entity<ApplicationUser>().HasData(dummyData.Users);
            builder.Entity<IdentityUserRole<string>>().HasData(dummyData.UserRolesMappings);
            builder.Entity<ImageCategory>().HasData(dummyData.ImageCategories);
            builder.Entity<Image>().HasData(dummyData.Images);
            builder.Entity<Keyword>().HasData(dummyData.Keywords);
            builder.Entity<Topic>().HasData(dummyData.Topics);
            builder.Entity<Summary>().HasData(dummyData.Summaries);
            builder.Entity<Chapter>().HasData(dummyData.Chapters);
            builder.Entity<Section>().HasData(dummyData.Sections);
            builder.Entity<Article>().HasData(dummyData.Articles);
            builder.Entity<SectionArticle>().HasData(dummyData.SectionArticles);
            builder.Entity<Paragraph>().HasData(dummyData.Paragraphs);
            builder.Entity<SummaryAlinea>().HasData(dummyData.SummaryAlineas);
            builder.Entity<ParagraphAlinea>().HasData(dummyData.ParagraphAlineas);
            //builder.Entity<object>("TopicKeyword").HasData(dummyData.TopicsKeywords);
        }

I'm not sure what to do with the last line.
Here's my build data sample method:

        public DummyDataModel BuildDataSample(int? count = null)
        {
            var data = new DummyDataModel();
            var superAdminId = _actualData.Users.FirstOrDefault(u => u.UserName == "SuperAdmin").Id;
            data.Users = new DummyUserDataSetupService().SetData().ToHashSet();
            data.UserRolesMappings = new DummyUserRoleAssociationService().AssignRolesToUserCollection(data.Users, _actualData.Roles).ToHashSet();
            data.ImageCategories = new DummyImageCategoriesGeneratorService(superAdminId).GenerateEntities(3).ToHashSet();
            data.Images = new DummyImagesGeneratorService(data.ImageCategories, superAdminId).GenerateEntities(100).ToHashSet();
            data.Keywords = new DummyKeywordsGeneratorService(superAdminId).GenerateEntities(100).ToHashSet();
            data.Topics = new DummyTopicsGeneratorService(_actualData.Themes, data.Keywords, superAdminId).GenerateEntities(count).ToHashSet();
            data.TopicsKeywords = new DummyTopicKeywordAssociationService(data.Topics, data.Keywords).AssignKeywordsToTopicCollection().ToHashSet();

            //data.Summaries = new DummySumariesGeneratorService(data.Topics, superAdminId).GenerateEntities().ToHashSet();

            return data;
        }

Here's my set up associations method :

        /// <summary>
        /// Assigns keywords to multiple topics.
        /// </summary>
        /// <returns>An enumeration of keyword-topic associations.</returns>
        public IEnumerable<object> AssignKeywordsToTopicCollection()
        {
            foreach (var topic in _topics)
                yield return AssignKeywordsToTopic(topic);
        }

        /// <summary>
        /// Assigns keywords to a specific topic.
        /// </summary>
        /// <param name="topic">The topic to assign keywords to.</param>
        /// <returns>An enumeration of keyword-topic associations.</returns>
        private IEnumerable<object> AssignKeywordsToTopic(Topic topic)
        {
            var number = new Random().Next(13) - 5;
            if (number <= 0)
                yield return null;

            for (int i = 0; i < number; i++)
                yield return new
                {
                    KeywordsId = _keywords.ElementAt(new Random().Next(_keywords.Count() - 1)).Id,
                    TopicId = topic.Id,
                };
        }

Does anyone have an idea?

Thank you in advance.

Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
732 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,846 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Laurent Guigon 281 Reputation points
    2023-09-07T07:35:08.3166667+00:00

    Hello I found a solution!
    Here's my new code:

    • In the OnModelCreating Method (DBContext):
                modelBuilder.Entity<Topic>()
                    .HasMany(t => t.Keywords)
                    .WithMany(k => k.Topics)
                    .UsingEntity(j => j.ToTable("TopicKeyword"));
    
    • In the Seed Method:
                builder.Entity<Topic>().HasMany(t => t.Keywords).WithMany(k => k.Topics).UsingEntity(tk => tk.HasData(dummyData.TopicsKeywords));
     ...
    
    
       
    - In the BuildDataSample (DummyDataGeneratorService):
    
    ...c#
                data.TopicsKeywords = new DummyTopicKeywordAssociationService(data.Topics, data.Keywords).AssignKeywordsToTopicCollection().ToHashSet();
    
    
    • And the final class:
        public class DummyTopicKeywordAssociationService
        {
            private readonly IEnumerable<Topic> _topics;
            private readonly IEnumerable<Keyword> _keywords;
            private readonly Random _random;
    
            public DummyTopicKeywordAssociationService(IEnumerable<Topic> topics, IEnumerable<Keyword> keywords)
            {
                _topics = topics;
                _keywords = keywords;
                _random = new Random();
            }
    
            public IEnumerable<object> AssignKeywordsToTopicCollection()
            {
                return _topics
                    .SelectMany(topic => AssignKeywordsToTopic(topic))
                    .Where(association => association != null);
            }
    
            private IEnumerable<object> AssignKeywordsToTopic(Topic topic)
            {
                var number = _random.Next(13) - 5;
                if (number <= 0)
                    yield break;
    
                for (int i = 0; i < number; i++)
                    yield return new
                    {
                        TopicsId = topic.Id,
                        KeywordsId = _keywords.ElementAt(_random.Next(_keywords.Count() - 1)).Id
                    };
            }
        }
    
    1 person found this answer helpful.
    0 comments No comments

  2. Karen Payne MVP 35,401 Reputation points
    2023-09-02T16:59:50.4633333+00:00

    Conceptually speaking if you want related data, use HasData. This can be done with static data or mocked up say with Bogus.

    • Create a mock data class, with methods to use static or dynamic random data
    • Write logic to use the above data, below is a simple example in a DbContext but does not need to be there. Category and Product are related and you can go as deep as needed.
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Category>().HasData(MockedData.Categories());
        modelBuilder.Entity<Countries>().HasData(MockedData.Countries());
        modelBuilder.Entity<Product>().HasData(MockedData.Products());
    }
    

    If needed couple with EnsureDeleted and EnsureCreated and each have async versions.

    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();
    

  3. AgaveJoe 28,041 Reputation points
    2023-09-02T14:38:15.64+00:00

    If I understand correctly, you've created many-to-many relationship and are having trouble seeding the table that relates Keyword and Topic tables.

    The following example is based on the official many-to-many documentation.

        public class Post
        {
            public int Id { get; set; }
            public List<Tag> Tags { get; } = new();
        }
    
        public class Tag
        {
            public int Id { get; set; }
            public List<Post> Posts { get; } = new();
        }
    
        public class PostTag
        {
            public int PostsId { get; set; }
            public int TagsId { get; set; }
            public Post Post { get; set; } = null!;
            public Tag Tag { get; set; } = null!;
        }
    
        public class ApplicationDbContext : DbContext
        {
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Post>()
                    .HasMany(e => e.Tags)
                    .WithMany(e => e.Posts)
                    .UsingEntity<PostTag>(); ;
            }
    
            public DbSet<Tag> Tags => Set<Tag>();
            public DbSet<Post> Posts => Set<Post>();
            public DbSet<PostTag> PostTags => Set<PostTag>();
        }
    

    Seed data

        public class SeedData
        {
            public static async Task InitializeAsync(IServiceProvider serviceProvider)
            {
                using (var context = new ApplicationDbContext(serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
                {
    
                    if(context == null || context.PostTags == null) 
                    {
                        throw new ArgumentNullException("Null ApplicationDbContext -> Posts");
                    }
    
                    if (!context.PostTags.Any())
                    {
                        var postTags = new List<PostTag>()
                        {
                            new PostTag() 
                            {
                                Post = new Post() { },
                                Tag = new Tag() { }
                            },
                             new PostTag()
                            {
                                Post = new Post() { },
                                Tag = new Tag() { }
                            },
                            new PostTag()
                            {
                                Post = new Post() { },
                                Tag = new Tag() { }
                            }
                        };
    
                        context.AddRange(postTags);
                        await context.SaveChangesAsync();
                    }
    
                }
    
            }
        }
    

    Program.cs

    var app = builder.Build();
    
    using (var scope = app.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
    
        await WebApiSqlite.Data.SeedData.InitializeAsync(services);
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.