我想使用通用方法将数据导入到我的应用程序中。
例如,说我有:
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方法,有可能实现吗?
答案 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)。