Общий метод импорта для EF - PullRequest
0 голосов
/ 07 июня 2019

Я хотел бы сделать универсальный метод для импорта данных в мое приложение.

Например, скажем, у меня есть:

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 будет рассматриваемой БД.

У всех моих классов есть одна общая черта, у всех них есть конструктор, которыйзанимает 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 ]

2 голосов
/ 07 июня 2019

Самый простой способ сделать это - передать универсальную функцию, которая принимает строковую строку или строковый массив разделенных значений и возвращает объект с установленными значениями. Используйте метод 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 голос
/ 07 июня 2019

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

Обратите внимание, если вы все еще хотите использовать этосо словарем необходимо будет использовать отражение ( пример ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...