Предположим, у вас есть список Category
для импорта, вы можете сначала получить список идентификаторов этих категорий, а затем запросить базу данных, чтобы убедиться, что она уже сохранена в базе данных. А для тех, которые уже существуют, просто пропустите их (или обновите их, как вам нравится).
Так как у нас есть несколько типов сущностей (категорий, продуктов, счетов и потенциальных счетов-фактур), а не пишем новый импортер для каждого TEntity
, я предпочитаю писать общий метод Importer
для работы со списком типов сущностей с Generic
и 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;
}
Здесь GetId<TEntity,TKey>(TEntity e)
- это простой вспомогательный метод, который используется для получения key
поля 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 ;
}
Чтобы сделать код более пригодным для повторного использования, мы можем создать сервис EntityImporter
для метода, описанного выше:
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>
{
// ...
}
}
, а затем зарегистрировать службы во время запуска:
services.AddScoped<DbContext, AppDbContext>();
services.AddScoped<EntityImporter>();
Контрольный пример:
Во-первых, я возьму несколько категорий в качестве примера:
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);
Ожидаемые результаты должны быть импортированы только в 3 строки:
1 category #1
2 category #2
3 category #3
А вот скриншот, он работает:
Наконец, для ваших счетов json вы можете сделать следующее, чтобы импортировать энтиты:
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