Entity Framework Core import / export tree of entities with similar foreign keys
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
add a comment |
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
add a comment |
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
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
asp.net-core .net-core entity-framework-core entity
edited Nov 14 '18 at 21:13
marc_s
578k12911171263
578k12911171263
asked Nov 14 '18 at 19:42
Fabien DezautezFabien Dezautez
366
366
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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:

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
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 ofId:99, Name:"Category #1"within you json is exactly the same asId: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 thesrc'id has already been taken bydestdatabase, 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
|
show 6 more comments
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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:

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
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 ofId:99, Name:"Category #1"within you json is exactly the same asId: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 thesrc'id has already been taken bydestdatabase, 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
|
show 6 more comments
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:

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
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 ofId:99, Name:"Category #1"within you json is exactly the same asId: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 thesrc'id has already been taken bydestdatabase, 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
|
show 6 more comments
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:

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
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:

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
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 ofId:99, Name:"Category #1"within you json is exactly the same asId: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 thesrc'id has already been taken bydestdatabase, 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
|
show 6 more comments
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 ofId:99, Name:"Category #1"within you json is exactly the same asId: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 thesrc'id has already been taken bydestdatabase, 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
|
show 6 more comments
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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