Вызывать метод сразу после построения объекта в запросе LINQ - PullRequest
4 голосов
/ 02 мая 2010

У меня есть несколько объектов, которые реализуют этот интерфейс:

public interface IRow
{
  void Fill(DataRow dr);
}

Обычно, когда я выбираю что-то из БД, я иду:

public IEnumerable<IRow> SelectSomeRows
{
  DataTable table = GetTableFromDatabase();
  foreach (DataRow dr in table.Rows)
  {
    IRow row = new MySQLRow(); // Disregard the MySQLRow type, it's not important
    row.Fill(dr);
    yield return row;
  }
}

Теперь с .Net 4 я бы хотел использовать AsParallel и, следовательно, LINQ.

Я провел некоторое тестирование на нем, и это сильно ускоряет процесс (IRow.Fill использует Reflection, поэтому он сильно загружает процессор)

В любом случае, моя проблема в том, как мне создать запрос LINQ, который вызывает Fills как часть запроса, поэтому он правильно распараллелен?

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

С конструктором на месте все достаточно просто:

public IEnumerable<IRow> SelectSomeRowsParallel
{
  DataTable table = GetTableFromDatabase();
  return from DataRow dr in table.Rows.AsParallel()
         select new MySQLRow(dr);
}

Однако, как я уже сказал, я бы очень хотел иметь возможность просто вставить свой метод Fill в запрос LINQ и, следовательно, не нуждаться в перегрузке конструктора.

Ответы [ 3 ]

4 голосов
/ 02 мая 2010

Вам нужно сделать лямбда-выражение из нескольких операторов, например:

table.AsEnumerable().AsParallel().Select(dr => 
    IRow row = new MySQLRow(); 
    row.Fill(dr);
    return row;
});
1 голос
/ 02 мая 2010

Я не думаю, что есть какой-либо способ поместить императивную операцию (например, вызов метода Fill, который возвращает void) в синтаксис запроса LINQ, но вы можете сделать то же самое, используя явный вызов метода Select, который позволяет использовать произвольный код:

DataTable table = GetTableFromDatabase(); 
return table.Rows.Cast<DataRow>().AsParallel().Select(dr => {
  IRow row = new MySQLRow(); 
  row.Fill(dr); 
  return dr; });

Вам необходимо добавить вызов к Cast (потому что DataSets не реализует универсальную версию IEnumerable), а остальная часть кода довольно проста. Ваш оригинальный запрос будет переводиться именно на эти звонки.

Если вы хотите сделать несколько трюков, вы можете изменить интерфейс так, чтобы метод Fill что-то возвращал (например, int). Тогда вы можете использовать предложение let и игнорировать возвращаемое значение.

return from DataRow dr in table.AsParallel() 
       let IRow row = new MySQLRow()
       let _ = row.Fill(dr) // ignoring return value; '_' is just variable name
       select row;

Можно использовать этот трюк для вызова методов, которые возвращают что-то, но не для методов, которые возвращают void.

1 голос
/ 02 мая 2010

К счастью, ответ очень прост. Просто сделайте это :) Ничто не мешает вам просто вызвать метод в выделенной части запроса

public IEnumerable<IRow> SelectSomeRowsParallel
        {
          DataTable table = GetTableFromDatabase();
          return from DataRow dr in table.Rows.AsParallel()
                 select (row => 
                         var mysqlRow = new MySQLRow()
                         mysqlRow.Fill(row);
                         return mysqlRow;)
        }

Я не уверен, что вы можете вставить там лямбду (прошло несколько лет с тех пор, как у меня была возможность написать LINQ), если вы не можете назначить ее на Func

Func<IRow,DataRow> getRow = 
                     (row => 
                     var mysqlRow = new MySQLRow()
                     mysqlRow.Fill(row);
                     return mysqlRow;)

и затем назовите это в предложении выбора

...