Entity Framework Core import / export tree of entities with similar foreign keys










1















I need to add an import/export functionality to my ASP.NET Core application.



What I would like is to take entities in one database, export these entities into one file, and then import that file into a new database.



My problem is that I have some entities that hold same foreign key. Here is a simple model illustrating what I want to do:



public class Bill 

public List<Product> Products get; set;
public int Id get; set;
...


public class Product

public int Id get; set;
public int ProductCategoryId get; set;
public ProductCategory ProductCategory get; set;
...


public class Category

public int Id get; set;
public string Name get; set;



I want to export a bill to be imported on an other environment of my application. So if I export the bill, I will get a Json like this:




"id": 1,
"products" : [

"id" : 1,
"productCategoryId": 1,
"productCategory":
"id" : 1,
"name" : "Category #1"

,

"id" : 2,
"productCategoryId": 1,
"productCategory":
"id" : 1,
"name" : "Category #1"

,

"id" : 3,
"productCategoryId": 1,
"productCategory":
"id" : 2,
"name" : "Category #2"


]



If I deserialize this json into entities in my new environment (ignoring Ids mapping of course), I will get three new categories (category will be duplicated for product 1 and 2) because the serializer will instanciate two categories...



So when I push it into my database, it will add 3 lines instead 2 into the Category table...



Thanks in advance for your answers.










share|improve this question




























    1















    I need to add an import/export functionality to my ASP.NET Core application.



    What I would like is to take entities in one database, export these entities into one file, and then import that file into a new database.



    My problem is that I have some entities that hold same foreign key. Here is a simple model illustrating what I want to do:



    public class Bill 

    public List<Product> Products get; set;
    public int Id get; set;
    ...


    public class Product

    public int Id get; set;
    public int ProductCategoryId get; set;
    public ProductCategory ProductCategory get; set;
    ...


    public class Category

    public int Id get; set;
    public string Name get; set;



    I want to export a bill to be imported on an other environment of my application. So if I export the bill, I will get a Json like this:




    "id": 1,
    "products" : [

    "id" : 1,
    "productCategoryId": 1,
    "productCategory":
    "id" : 1,
    "name" : "Category #1"

    ,

    "id" : 2,
    "productCategoryId": 1,
    "productCategory":
    "id" : 1,
    "name" : "Category #1"

    ,

    "id" : 3,
    "productCategoryId": 1,
    "productCategory":
    "id" : 2,
    "name" : "Category #2"


    ]



    If I deserialize this json into entities in my new environment (ignoring Ids mapping of course), I will get three new categories (category will be duplicated for product 1 and 2) because the serializer will instanciate two categories...



    So when I push it into my database, it will add 3 lines instead 2 into the Category table...



    Thanks in advance for your answers.










    share|improve this question


























      1












      1








      1








      I need to add an import/export functionality to my ASP.NET Core application.



      What I would like is to take entities in one database, export these entities into one file, and then import that file into a new database.



      My problem is that I have some entities that hold same foreign key. Here is a simple model illustrating what I want to do:



      public class Bill 

      public List<Product> Products get; set;
      public int Id get; set;
      ...


      public class Product

      public int Id get; set;
      public int ProductCategoryId get; set;
      public ProductCategory ProductCategory get; set;
      ...


      public class Category

      public int Id get; set;
      public string Name get; set;



      I want to export a bill to be imported on an other environment of my application. So if I export the bill, I will get a Json like this:




      "id": 1,
      "products" : [

      "id" : 1,
      "productCategoryId": 1,
      "productCategory":
      "id" : 1,
      "name" : "Category #1"

      ,

      "id" : 2,
      "productCategoryId": 1,
      "productCategory":
      "id" : 1,
      "name" : "Category #1"

      ,

      "id" : 3,
      "productCategoryId": 1,
      "productCategory":
      "id" : 2,
      "name" : "Category #2"


      ]



      If I deserialize this json into entities in my new environment (ignoring Ids mapping of course), I will get three new categories (category will be duplicated for product 1 and 2) because the serializer will instanciate two categories...



      So when I push it into my database, it will add 3 lines instead 2 into the Category table...



      Thanks in advance for your answers.










      share|improve this question
















      I need to add an import/export functionality to my ASP.NET Core application.



      What I would like is to take entities in one database, export these entities into one file, and then import that file into a new database.



      My problem is that I have some entities that hold same foreign key. Here is a simple model illustrating what I want to do:



      public class Bill 

      public List<Product> Products get; set;
      public int Id get; set;
      ...


      public class Product

      public int Id get; set;
      public int ProductCategoryId get; set;
      public ProductCategory ProductCategory get; set;
      ...


      public class Category

      public int Id get; set;
      public string Name get; set;



      I want to export a bill to be imported on an other environment of my application. So if I export the bill, I will get a Json like this:




      "id": 1,
      "products" : [

      "id" : 1,
      "productCategoryId": 1,
      "productCategory":
      "id" : 1,
      "name" : "Category #1"

      ,

      "id" : 2,
      "productCategoryId": 1,
      "productCategory":
      "id" : 1,
      "name" : "Category #1"

      ,

      "id" : 3,
      "productCategoryId": 1,
      "productCategory":
      "id" : 2,
      "name" : "Category #2"


      ]



      If I deserialize this json into entities in my new environment (ignoring Ids mapping of course), I will get three new categories (category will be duplicated for product 1 and 2) because the serializer will instanciate two categories...



      So when I push it into my database, it will add 3 lines instead 2 into the Category table...



      Thanks in advance for your answers.







      asp.net-core .net-core entity-framework-core entity






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 14 '18 at 21:13









      marc_s

      578k12911171263




      578k12911171263










      asked Nov 14 '18 at 19:42









      Fabien DezautezFabien Dezautez

      366




      366






















          1 Answer
          1






          active

          oldest

          votes


















          0














          Suppose you have a list of Category to be imported, you could firstly get the id list of these categories and then query the database to make sure which has been already stored in database. And for those already existing ones, just skip them (or update them as you like).



          Since we have multiple entity types (categories,products,bills and potential BillProducts), rather than writing a new importer for each TEntity , I prefer to writing a generic Importer method to deal with any Entity type list with Generic and Reflection :



          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          var ids = entites.Select( e=> GetId<TEntity,TKey>(e));
          var existingsInDatabase=this._context.Set<TEntity>()
          .Where(e=> ids.Any(i => i.Equals(GetId<TEntity,TKey>(e)) ))
          .ToList();

          using (var transaction = this._context.Database.BeginTransaction())

          try
          this._context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TEntity).Name + " ON;");
          this._context.SaveChanges();

          foreach (var entity in entites)

          var e= existingsInDatabase.Find(existing =>
          var k1 =GetId<TEntity,TKey>(existing);
          var k2=GetId<TEntity,TKey>(entity);
          return k1.Equals(k2);
          );
          // if not yet exists
          if(e == null)
          this._context.Add(entity);
          else
          // if you would like to update the old one when there're some differences
          // uncomment the following line :
          // this._context.Entry(e).CurrentValues.SetValues(entity);


          await this._context.SaveChangesAsync();
          transaction.Commit();

          catch
          transaction.Rollback();

          finally
          this._context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TEntity).Name + " OFF;");
          await this._context.SaveChangesAsync();


          return;



          Here the GetId<TEntity,TKey>(TEntity e) is a simple helper method which is used to get the key filed of e:



           // use reflection to get the Id of any TEntity type
          private static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          PropertyInfo pi=typeof(TEntity).GetProperty("Id");
          if(pi == null) throw new Exception($"the type typeof(TEntity) must has a property of `Id`");
          TKey k = (TKey) pi.GetValue(e);
          return k ;



          To make the code more reusable, we can create an EntityImporter service to hold the method above :



          public class EntityImporter
          private DbContext _context;

          public EntityImporter(DbContext dbContext)
          this._context = dbContext;


          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          // ...


          public static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          // ...




          and then register the services at startup time:



          services.AddScoped<DbContext, AppDbContext>();
          services.AddScoped<EntityImporter>();


          Test Case :



          Firstly, I'll take several categories as an example :



          var categories = new ProductCategory
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 3,
          Name="Category #3"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          ;

          await this._importer.ImportBatch<ProductCategory,int>(categories);


          The expected results should be only 3 rows imported :



          1 category #1
          2 category #2
          3 category #3


          And here's a screenshot it works:



          enter image description here



          Finally, for your bills json, you can do as below to import the entites :



          var categories = bill.Products.Select(p=>p.ProductCategory).ToList();
          var products = bill.Products.ToList();
          // other List<TEntity>...

          // import the categories firstly , since they might be referenced by other entity
          await this._importer.ImportBatch<ProductCategory,int>(categories);
          // import the product secondly , since they might be referenced by BillProduct table
          await this._import.ImportBatch<Product,int>(products);
          // ... import others





          share|improve this answer























          • Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

            – Fabien Dezautez
            Nov 15 '18 at 8:10












          • @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

            – itminus
            Nov 15 '18 at 8:19












          • No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

            – Fabien Dezautez
            Nov 15 '18 at 8:21











          • @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

            – itminus
            Nov 15 '18 at 8:27











          • @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

            – itminus
            Nov 15 '18 at 8:31











          Your Answer






          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "1"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader:
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          ,
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );













          draft saved

          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53307683%2fentity-framework-core-import-export-tree-of-entities-with-similar-foreign-keys%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          0














          Suppose you have a list of Category to be imported, you could firstly get the id list of these categories and then query the database to make sure which has been already stored in database. And for those already existing ones, just skip them (or update them as you like).



          Since we have multiple entity types (categories,products,bills and potential BillProducts), rather than writing a new importer for each TEntity , I prefer to writing a generic Importer method to deal with any Entity type list with Generic and Reflection :



          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          var ids = entites.Select( e=> GetId<TEntity,TKey>(e));
          var existingsInDatabase=this._context.Set<TEntity>()
          .Where(e=> ids.Any(i => i.Equals(GetId<TEntity,TKey>(e)) ))
          .ToList();

          using (var transaction = this._context.Database.BeginTransaction())

          try
          this._context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TEntity).Name + " ON;");
          this._context.SaveChanges();

          foreach (var entity in entites)

          var e= existingsInDatabase.Find(existing =>
          var k1 =GetId<TEntity,TKey>(existing);
          var k2=GetId<TEntity,TKey>(entity);
          return k1.Equals(k2);
          );
          // if not yet exists
          if(e == null)
          this._context.Add(entity);
          else
          // if you would like to update the old one when there're some differences
          // uncomment the following line :
          // this._context.Entry(e).CurrentValues.SetValues(entity);


          await this._context.SaveChangesAsync();
          transaction.Commit();

          catch
          transaction.Rollback();

          finally
          this._context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TEntity).Name + " OFF;");
          await this._context.SaveChangesAsync();


          return;



          Here the GetId<TEntity,TKey>(TEntity e) is a simple helper method which is used to get the key filed of e:



           // use reflection to get the Id of any TEntity type
          private static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          PropertyInfo pi=typeof(TEntity).GetProperty("Id");
          if(pi == null) throw new Exception($"the type typeof(TEntity) must has a property of `Id`");
          TKey k = (TKey) pi.GetValue(e);
          return k ;



          To make the code more reusable, we can create an EntityImporter service to hold the method above :



          public class EntityImporter
          private DbContext _context;

          public EntityImporter(DbContext dbContext)
          this._context = dbContext;


          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          // ...


          public static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          // ...




          and then register the services at startup time:



          services.AddScoped<DbContext, AppDbContext>();
          services.AddScoped<EntityImporter>();


          Test Case :



          Firstly, I'll take several categories as an example :



          var categories = new ProductCategory
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 3,
          Name="Category #3"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          ;

          await this._importer.ImportBatch<ProductCategory,int>(categories);


          The expected results should be only 3 rows imported :



          1 category #1
          2 category #2
          3 category #3


          And here's a screenshot it works:



          enter image description here



          Finally, for your bills json, you can do as below to import the entites :



          var categories = bill.Products.Select(p=>p.ProductCategory).ToList();
          var products = bill.Products.ToList();
          // other List<TEntity>...

          // import the categories firstly , since they might be referenced by other entity
          await this._importer.ImportBatch<ProductCategory,int>(categories);
          // import the product secondly , since they might be referenced by BillProduct table
          await this._import.ImportBatch<Product,int>(products);
          // ... import others





          share|improve this answer























          • Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

            – Fabien Dezautez
            Nov 15 '18 at 8:10












          • @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

            – itminus
            Nov 15 '18 at 8:19












          • No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

            – Fabien Dezautez
            Nov 15 '18 at 8:21











          • @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

            – itminus
            Nov 15 '18 at 8:27











          • @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

            – itminus
            Nov 15 '18 at 8:31
















          0














          Suppose you have a list of Category to be imported, you could firstly get the id list of these categories and then query the database to make sure which has been already stored in database. And for those already existing ones, just skip them (or update them as you like).



          Since we have multiple entity types (categories,products,bills and potential BillProducts), rather than writing a new importer for each TEntity , I prefer to writing a generic Importer method to deal with any Entity type list with Generic and Reflection :



          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          var ids = entites.Select( e=> GetId<TEntity,TKey>(e));
          var existingsInDatabase=this._context.Set<TEntity>()
          .Where(e=> ids.Any(i => i.Equals(GetId<TEntity,TKey>(e)) ))
          .ToList();

          using (var transaction = this._context.Database.BeginTransaction())

          try
          this._context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TEntity).Name + " ON;");
          this._context.SaveChanges();

          foreach (var entity in entites)

          var e= existingsInDatabase.Find(existing =>
          var k1 =GetId<TEntity,TKey>(existing);
          var k2=GetId<TEntity,TKey>(entity);
          return k1.Equals(k2);
          );
          // if not yet exists
          if(e == null)
          this._context.Add(entity);
          else
          // if you would like to update the old one when there're some differences
          // uncomment the following line :
          // this._context.Entry(e).CurrentValues.SetValues(entity);


          await this._context.SaveChangesAsync();
          transaction.Commit();

          catch
          transaction.Rollback();

          finally
          this._context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TEntity).Name + " OFF;");
          await this._context.SaveChangesAsync();


          return;



          Here the GetId<TEntity,TKey>(TEntity e) is a simple helper method which is used to get the key filed of e:



           // use reflection to get the Id of any TEntity type
          private static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          PropertyInfo pi=typeof(TEntity).GetProperty("Id");
          if(pi == null) throw new Exception($"the type typeof(TEntity) must has a property of `Id`");
          TKey k = (TKey) pi.GetValue(e);
          return k ;



          To make the code more reusable, we can create an EntityImporter service to hold the method above :



          public class EntityImporter
          private DbContext _context;

          public EntityImporter(DbContext dbContext)
          this._context = dbContext;


          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          // ...


          public static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          // ...




          and then register the services at startup time:



          services.AddScoped<DbContext, AppDbContext>();
          services.AddScoped<EntityImporter>();


          Test Case :



          Firstly, I'll take several categories as an example :



          var categories = new ProductCategory
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 3,
          Name="Category #3"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          ;

          await this._importer.ImportBatch<ProductCategory,int>(categories);


          The expected results should be only 3 rows imported :



          1 category #1
          2 category #2
          3 category #3


          And here's a screenshot it works:



          enter image description here



          Finally, for your bills json, you can do as below to import the entites :



          var categories = bill.Products.Select(p=>p.ProductCategory).ToList();
          var products = bill.Products.ToList();
          // other List<TEntity>...

          // import the categories firstly , since they might be referenced by other entity
          await this._importer.ImportBatch<ProductCategory,int>(categories);
          // import the product secondly , since they might be referenced by BillProduct table
          await this._import.ImportBatch<Product,int>(products);
          // ... import others





          share|improve this answer























          • Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

            – Fabien Dezautez
            Nov 15 '18 at 8:10












          • @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

            – itminus
            Nov 15 '18 at 8:19












          • No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

            – Fabien Dezautez
            Nov 15 '18 at 8:21











          • @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

            – itminus
            Nov 15 '18 at 8:27











          • @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

            – itminus
            Nov 15 '18 at 8:31














          0












          0








          0







          Suppose you have a list of Category to be imported, you could firstly get the id list of these categories and then query the database to make sure which has been already stored in database. And for those already existing ones, just skip them (or update them as you like).



          Since we have multiple entity types (categories,products,bills and potential BillProducts), rather than writing a new importer for each TEntity , I prefer to writing a generic Importer method to deal with any Entity type list with Generic and Reflection :



          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          var ids = entites.Select( e=> GetId<TEntity,TKey>(e));
          var existingsInDatabase=this._context.Set<TEntity>()
          .Where(e=> ids.Any(i => i.Equals(GetId<TEntity,TKey>(e)) ))
          .ToList();

          using (var transaction = this._context.Database.BeginTransaction())

          try
          this._context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TEntity).Name + " ON;");
          this._context.SaveChanges();

          foreach (var entity in entites)

          var e= existingsInDatabase.Find(existing =>
          var k1 =GetId<TEntity,TKey>(existing);
          var k2=GetId<TEntity,TKey>(entity);
          return k1.Equals(k2);
          );
          // if not yet exists
          if(e == null)
          this._context.Add(entity);
          else
          // if you would like to update the old one when there're some differences
          // uncomment the following line :
          // this._context.Entry(e).CurrentValues.SetValues(entity);


          await this._context.SaveChangesAsync();
          transaction.Commit();

          catch
          transaction.Rollback();

          finally
          this._context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TEntity).Name + " OFF;");
          await this._context.SaveChangesAsync();


          return;



          Here the GetId<TEntity,TKey>(TEntity e) is a simple helper method which is used to get the key filed of e:



           // use reflection to get the Id of any TEntity type
          private static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          PropertyInfo pi=typeof(TEntity).GetProperty("Id");
          if(pi == null) throw new Exception($"the type typeof(TEntity) must has a property of `Id`");
          TKey k = (TKey) pi.GetValue(e);
          return k ;



          To make the code more reusable, we can create an EntityImporter service to hold the method above :



          public class EntityImporter
          private DbContext _context;

          public EntityImporter(DbContext dbContext)
          this._context = dbContext;


          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          // ...


          public static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          // ...




          and then register the services at startup time:



          services.AddScoped<DbContext, AppDbContext>();
          services.AddScoped<EntityImporter>();


          Test Case :



          Firstly, I'll take several categories as an example :



          var categories = new ProductCategory
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 3,
          Name="Category #3"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          ;

          await this._importer.ImportBatch<ProductCategory,int>(categories);


          The expected results should be only 3 rows imported :



          1 category #1
          2 category #2
          3 category #3


          And here's a screenshot it works:



          enter image description here



          Finally, for your bills json, you can do as below to import the entites :



          var categories = bill.Products.Select(p=>p.ProductCategory).ToList();
          var products = bill.Products.ToList();
          // other List<TEntity>...

          // import the categories firstly , since they might be referenced by other entity
          await this._importer.ImportBatch<ProductCategory,int>(categories);
          // import the product secondly , since they might be referenced by BillProduct table
          await this._import.ImportBatch<Product,int>(products);
          // ... import others





          share|improve this answer













          Suppose you have a list of Category to be imported, you could firstly get the id list of these categories and then query the database to make sure which has been already stored in database. And for those already existing ones, just skip them (or update them as you like).



          Since we have multiple entity types (categories,products,bills and potential BillProducts), rather than writing a new importer for each TEntity , I prefer to writing a generic Importer method to deal with any Entity type list with Generic and Reflection :



          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          var ids = entites.Select( e=> GetId<TEntity,TKey>(e));
          var existingsInDatabase=this._context.Set<TEntity>()
          .Where(e=> ids.Any(i => i.Equals(GetId<TEntity,TKey>(e)) ))
          .ToList();

          using (var transaction = this._context.Database.BeginTransaction())

          try
          this._context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TEntity).Name + " ON;");
          this._context.SaveChanges();

          foreach (var entity in entites)

          var e= existingsInDatabase.Find(existing =>
          var k1 =GetId<TEntity,TKey>(existing);
          var k2=GetId<TEntity,TKey>(entity);
          return k1.Equals(k2);
          );
          // if not yet exists
          if(e == null)
          this._context.Add(entity);
          else
          // if you would like to update the old one when there're some differences
          // uncomment the following line :
          // this._context.Entry(e).CurrentValues.SetValues(entity);


          await this._context.SaveChangesAsync();
          transaction.Commit();

          catch
          transaction.Rollback();

          finally
          this._context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TEntity).Name + " OFF;");
          await this._context.SaveChangesAsync();


          return;



          Here the GetId<TEntity,TKey>(TEntity e) is a simple helper method which is used to get the key filed of e:



           // use reflection to get the Id of any TEntity type
          private static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          PropertyInfo pi=typeof(TEntity).GetProperty("Id");
          if(pi == null) throw new Exception($"the type typeof(TEntity) must has a property of `Id`");
          TKey k = (TKey) pi.GetValue(e);
          return k ;



          To make the code more reusable, we can create an EntityImporter service to hold the method above :



          public class EntityImporter
          private DbContext _context;

          public EntityImporter(DbContext dbContext)
          this._context = dbContext;


          public async Task ImportBatch<TEntity,TKey>(IList<TEntity> entites)
          where TEntity : class
          where TKey: IEquatable<TKey>

          // ...


          public static TKey GetId<TEntity,TKey>(TEntity e)
          where TEntity : class
          where TKey : IEquatable<TKey>

          // ...




          and then register the services at startup time:



          services.AddScoped<DbContext, AppDbContext>();
          services.AddScoped<EntityImporter>();


          Test Case :



          Firstly, I'll take several categories as an example :



          var categories = new ProductCategory
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 3,
          Name="Category #3"
          ,
          new ProductCategory
          Id = 2,
          Name="Category #2"
          ,
          new ProductCategory
          Id = 1,
          Name="Category #1"
          ,
          ;

          await this._importer.ImportBatch<ProductCategory,int>(categories);


          The expected results should be only 3 rows imported :



          1 category #1
          2 category #2
          3 category #3


          And here's a screenshot it works:



          enter image description here



          Finally, for your bills json, you can do as below to import the entites :



          var categories = bill.Products.Select(p=>p.ProductCategory).ToList();
          var products = bill.Products.ToList();
          // other List<TEntity>...

          // import the categories firstly , since they might be referenced by other entity
          await this._importer.ImportBatch<ProductCategory,int>(categories);
          // import the product secondly , since they might be referenced by BillProduct table
          await this._import.ImportBatch<Product,int>(products);
          // ... import others






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 15 '18 at 7:13









          itminusitminus

          3,8261421




          3,8261421












          • Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

            – Fabien Dezautez
            Nov 15 '18 at 8:10












          • @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

            – itminus
            Nov 15 '18 at 8:19












          • No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

            – Fabien Dezautez
            Nov 15 '18 at 8:21











          • @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

            – itminus
            Nov 15 '18 at 8:27











          • @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

            – itminus
            Nov 15 '18 at 8:31


















          • Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

            – Fabien Dezautez
            Nov 15 '18 at 8:10












          • @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

            – itminus
            Nov 15 '18 at 8:19












          • No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

            – Fabien Dezautez
            Nov 15 '18 at 8:21











          • @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

            – itminus
            Nov 15 '18 at 8:27











          • @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

            – itminus
            Nov 15 '18 at 8:31

















          Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

          – Fabien Dezautez
          Nov 15 '18 at 8:10






          Thx for your answer. The problem is that you import on the new database the same ids than on the previous... so i can get conflict ! What i want is to get so new ids generated by the destination database, that's why it is a bit hard to keep foreign keys... Because my only solution to say EntityFramework that two newly created entities are the same in database is to have exactly the same reference each time an entity is used as foreign key.

          – Fabien Dezautez
          Nov 15 '18 at 8:10














          @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

          – itminus
          Nov 15 '18 at 8:19






          @FabienDezautez, So, if I understand correctly, a category of Id:99, Name:"Category #1" within you json is exactly the same as Id:66, Name:"Category #1" in database ?

          – itminus
          Nov 15 '18 at 8:19














          No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

          – Fabien Dezautez
          Nov 15 '18 at 8:21





          No, the category 99 and 66 are different. But these ids come from the source database and can be already used on the destination database

          – Fabien Dezautez
          Nov 15 '18 at 8:21













          @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

          – itminus
          Nov 15 '18 at 8:27





          @FabienDezautez Think about you have [Id:66, Name:"Category #1",Id:99, Name:"Category #1"] within the source database, and the id=66 has already been taken by dest database , and the new inserted record for id=66 will be [Id:1106, Name:"Category #1"] , what about the id=99 from source database ? should they considered as the same ?

          – itminus
          Nov 15 '18 at 8:27













          @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

          – itminus
          Nov 15 '18 at 8:31






          @FabienDezautez If the src'id has already been taken by dest database, and their Names are exactly the same, should they be considered as the same ? If not, In which case , should we consider they are duplicated ?

          – itminus
          Nov 15 '18 at 8:31




















          draft saved

          draft discarded
















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid


          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.

          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53307683%2fentity-framework-core-import-export-tree-of-entities-with-similar-foreign-keys%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          這個網誌中的熱門文章

          What does pagestruct do in Eviews?

          Dutch intervention in Lombok and Karangasem

          Channel Islands