How avoid adding duplicates to database managed by EntityFramework caused by Seed method?

13,062

Solution 1

From this page (about halfway down), which was sourced from this answer

Note: Adding code to the Seed method is one of many ways that you can insert fixed data into the database. An alternative is to add code to the Up and Down methods of each migration class. The Up and Down methods contain code that implements database changes. You'll see examples of them in the Deploying a Database Update tutorial.

You can also write code that executes SQL statements by using the Sql method. For example, if you were adding a Budget column to the Department table and wanted to initialize all department budgets to $1,000.00 as part of a migration, you could add the folllowing line of code to the Up method for that migration:

Sql("UPDATE Department SET Budget = 1000");

You might also look into using the AddOrUpdate method, as referenced in this answer, which should also work for your purposes.

I quickly changed the code I obtained from the answer linked above, so bear with me if there's an issue with the code below. The concept should still be relatively clear, I believe.

context.People.AddOrUpdate(c => c.PK, new Person() { PK = 0, FirstName = "John", ... })
context.People.AddOrUpdate(c => c.PK, new Person() { PK = 1, FirstName = "Anna", ... })

Solution 2

You have full access to the context in the Seed method, so you can query to see if data already exists.

For example, you can seed the tables only if they're empty...

protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {

    if (!context.Persons.Any())
    {   
        var persons = new List<Person> { 
            new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
            new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
        };

        persons.ForEach(person => context.Persons.Add(person));
        context.SaveChanges();
    }

    if (!context.Meetings.Any())
    {
        var meetings = new List<Meeting>{
            new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
        };

        meetings.ForEach(meeting => context.Meetings.Add(meeting));
        context.SaveChanges();
    }

    if (!context.Statuses.Any())
    {
        var statuses = new List<Status> {
            new Status{Name = "OK"},
            new Status {Name = "NOT_OK"}
        };

        statuses.ForEach(status => context.Statuses.Add(status));
        context.SaveChanges();
    }

}

You can also use AddOrUpdate, but you need to tell EF how to check if the record exists using the first parameter...

protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {

    var persons = new List<Person> { 
        new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
        new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
    };

    persons.ForEach(person => context.Persons.AddOrUpdate(p => new { p.FirstName, p.LastName }, person));
    context.SaveChanges();

    var meetings = new List<Meeting>{
        new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
    };

    meetings.ForEach(meeting => context.Meetings.AddOrUpdate(m => m.Body, meeting));
    context.SaveChanges();

    var statuses = new List<Status> {
        new Status{Name = "OK"},
        new Status {Name = "NOT_OK"}
    };

    statuses.ForEach(status => context.Statuses.AddOrUpdate(s => s.Name, status));
    context.SaveChanges();

}

Solution 3

Question: (1) What should I change so Seed method will be run only to recreate database after migration?

If you only need to seed data when your database is created. In this case, you can create a Database Initialiser from CreateDatabaseIfNotExist Initialiser. Then in the DatabaseInitialiser class, you can override the Seed Method with your data there, instead of the MigrationConfiguration class. Further information can be found in attached link.

Database Initialization Strategies in Code-First:

but my method is called ALWAYS, not only after migrations. Why is it so?

In migration configuration. the seed method will be called every time the database migration happens. That is why your seed method is called all the time.

Share:
13,062
Yoda
Author by

Yoda

If you have a question about Bonjour in .NET and AXIS SDK I am the guy. I HATE telerik.

Updated on June 07, 2022

Comments

  • Yoda
    Yoda almost 2 years

    Every time I run the application same objects are added to the database(duplicates).

    My Global.asax:

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Optimization;
    using System.Web.Routing;
    using WebApplication2.Migrations;
    using WebApplication2.Models;
    
    
    namespace WebApplication2 {
        public class MvcApplication : System.Web.HttpApplication {
            protected void Application_Start() {
                Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>()); 
                //Database.SetInitializer(new DropCreateDatabaseAlways<ApplicationDbContext>());
                AreaRegistration.RegisterAllAreas();
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }
    }
    

    and My Configuration.cs with Seed method:

    namespace WebApplication2.Migrations
    {
        using System;
        using System.Collections.Generic;
        using System.Data.Entity;
        using System.Data.Entity.Migrations;
        using System.Linq;
        using WebApplication2.Models;
    
        internal sealed class Configuration : DbMigrationsConfiguration<WebApplication2.Models.ApplicationDbContext>
        {
            public Configuration()
            {
                AutomaticMigrationsEnabled = true;
                ContextKey = "WebApplication2.Models.ApplicationDbContext";
            }
    
            protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {
                var persons = new List<Person> { 
             new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
             new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
            };
    
            persons.ForEach(person => context.Persons.AddOrUpdate(person));
            context.SaveChanges();
    
            var meetings = new List<Meeting>{
                new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
            };
    
            meetings.ForEach(meeting => context.Meetings.AddOrUpdate(meeting));
            context.SaveChanges();
    
            var statuses = new List<Status> {
                new Status{Name = "OK"},
                new Status {Name = "NOT_OK"}
            };
    
            statuses.ForEach(status => context.Statuses.AddOrUpdate(status));
            context.SaveChanges();
    
            }
        }
    }
    

    Every time I run the app Seed adds duplicate records:

    enter image description here

    I needed to comment contents of Seed method to prevent adding duplicates.

    Question: (1) What should I change so Seed method will be run only to recreate database after migration?

    EDIT:

    In the Seed method there is comment:

      //  This method will be called after migrating to the latest version.
    
                //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
                //  to avoid creating duplicate seed data. E.g.
                //
                //    context.People.AddOrUpdate(
                //      p => p.FullName,
                //      new Person { FullName = "Andrew Peters" },
                //      new Person { FullName = "Brice Lambson" },
                //      new Person { FullName = "Rowan Miller" }
                //    );
                //
    

    but my method is called ALWAYS, not only after migrations. Why is it so?