我需要在ASP.NET Core应用程序中添加导入/导出功能。
我想将实体放入一个数据库中,将这些实体导出到一个文件中,然后将该文件导入新数据库中。
我的问题是我有一些拥有相同外键的实体。这是一个简单的模型,说明了我想做什么:
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; }
}
我想导出要在我的应用程序的其他环境上导入的账单。因此,如果我汇出帐单,我将得到一个像这样的Json:
{
"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"
}
}
]
}
如果我将这个json反序列化为我的新环境中的实体(当然忽略IDs映射),我将获得三个新类别(类别将与产品1和2重复),因为序列化程序将实例化两个类别... < / p>
因此,当我将其推送到数据库中时,它将在Category
表中添加3行而不是2行...
预先感谢您的回答。
答案 0 :(得分:0)
假设您有一个要导入的Category
列表,则可以首先获取这些类别的ID列表,然后查询数据库以确保已将其存储在数据库中。对于已经存在的那些,只需跳过它们(或根据需要更新它们)。
由于我们具有多种实体类型(类别,产品,清单和潜在的BillProducts),因此与其为每个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
的{{1}}档案:
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>();
预期结果应仅导入3行:
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);
这是它的屏幕截图:
最后,对于您的bills json,您可以执行以下操作导入实体:
1 category #1
2 category #2
3 category #3