EF的通用导入方法

时间:2019-06-07 11:59:35

标签: c# entity-framework-core

我想使用通用方法将数据导入到我的应用程序中。

例如,说我有:

private static async Task<int> ImportAccount(string filename)
{
    var totalRecords = await GetLineCount(filename);
    var ctx = new AccountContext();
    var count = 0;
    var records = 0;
    using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        using (var reader = new StreamReader(stream, Encoding.UTF8))
        {
            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                var data = line.Split('\t');
                var acc = new Account(data);
                await ctx.Accounts.AddAsync(acc);
                // need this to avoid using all the memory
                // maybe there is a smarter or beter way to do it
                // with 10k it uses about 500mb memory, 
                // files have million rows+
                if (count % 10000 == 1)
                {
                    records += result = await ctx.SaveChangesAsync();
                    if (result > 0)
                    {
                        ctx.Dispose();
                        ctx = new AccountContext();
                    }
                }
                count++;
            }
        }
    }
    await ctx.SaveChangesAsync();
    ctx.Dispose();
    return records;
}

在上面的示例中,我将数据从制表符分隔的文件导入到Accounts数据库中。

然后我具有属性,土地以及需要导入的其他许多数据库。

我不必像上面那样为每个数据库创建一个方法,而是想做类似的事情:

internal static readonly Dictionary<string, ??> FilesToImport = new Dictionary<string, ??>
{
    { "fullpath to file", ?? would be what I need to pass to T }
    ... more files ...
};
private static async Task<int> Import<T>(string filename)

其中T是有问题的数据库。

我所有的类都有1个共同点,它们都有一个使用string[] data的构造函数。

但是我不知道如何制作一种我可以接受的方法:

private static async Task<int> Import<T>(string filename)

然后可以执行以下操作:

var item = new T(data);
await ctx.Set<T>().AddAsync(item);

如果我没记错的话,我将无法使用参数实例化T。

如何制作这种通用的Import方法,有可能实现吗?

2 个答案:

答案 0 :(得分:2)

最简单的方法是传递一个通用函数,该函数接受字符串行或拆分值的字符串数组,并返回一个设置了值的对象。使用支持泛型的ctx.AddAsync()方法,然后将该实体添加到正确的集合中。

private static async Task<int> Import<T>(string filename, Func<string, T> transform) where T : class
{
    var totalRecords = await GetLineCount(filename);
    var ctx = new AccountContext();
    var count = 0;
    var records = 0;
    using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        using (var reader = new StreamReader(stream, Encoding.UTF8))
        {
            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                var data = line.Split("\t");
                var entity = transform(data);
                await ctx.AddAsync(entity);
                if (count % 10000 == 1)
                {
                    records += result = await ctx.SaveChangesAsync();
                    if (result > 0)
                    {
                        ctx.Dispose();
                        ctx = new AccountContext();
                    }
                }
                count++;
            }
        }
    }
    await ctx.SaveChangesAsync();
    ctx.Dispose();
    return records;
}

// Usage

Import(filename, splits => {
   / * do whatever you need to transform the data */
   return new Whatever(splits);
})

由于不能通过传递参数来构造泛型类型,因此必须在字典中将函数用作第二种类型。

Dictionary<string, Func<string, object>> FilesToImport = new Dictionary<string, Func<string, object>>{
  { "fullpath to file", data => new Account(data) },
  { "fullpath to file", data => new Whatever(data) },
  { "fullpath to file", data => new Whatever2(data) },
}

答案 1 :(得分:1)

C#仅对通用类型参数使用new()限制。但是不幸的是,不可能强制类型具有带有参数的构造函数。

一种解决方法是定义这样的接口:

interface IImportedEntity<T>
// where T: YourBaseClass
{
    T Init(string[] data);
}

在这种情况下,所有实现类都必须实现这种方法:

class Account : /*YourBaseClass*/ IImportedEntity<Account>
{
    public Account()
    {
        // for EF
    }

    // can be made private or protected
    public Account(string[] data)
    {
        // your code
    }


    // public Account Init(string[] data) => { /*populate current instance*/  return this;};
    // can be implemented in base class
    public Account Init(string[] data) => new Account(data);
}

最后,您可以将通用Import方法限制为仅处理导入的实体:

private static async Task<int> Import<T>(string filename) 
      where T: class, IImportedEntity<T>, new()
{
    ....
    var item = new T();
    item = item.Init(data);
    await ctx.Set<T>().AddAsync(item);
    ...
}

请注意,如果您仍然想将其与字典一起使用,则需要使用反射(example)。