具有相似外键的实体的实体框架核心导入/导出树

时间:2018-11-14 19:42:49

标签: asp.net-core .net-core entity-framework-core entity

我需要在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行...

预先感谢您的回答。

1 个答案:

答案 0 :(得分:0)

假设您有一个要导入的Category列表,则可以首先获取这些类别的ID列表,然后查询数据库以确保已将其存储在数据库中。对于已经存在的那些,只需跳过它们(或根据需要更新它们)。

由于我们具有多种实体类型(类别,产品,清单和潜在的BillProducts),因此与其为每个TEntity编写一个新的导入器,我更喜欢编写一个通用的Importer方法来处理任何带有GenericReflection的实体类型列表:

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);

这是它的屏幕截图:

enter image description here

最后,对于您的bills json,您可以执行以下操作导入实体:

1 category #1
2 category #2 
3 category #3
相关问题