2016-12-06 2 views
0

Я новичок в EF, поэтому извиняюсь в продвинутом, если что-то сделано неправильно. Я изо всех сил пытаюсь заставить пейджинг работать асинхронно с EF 6.Асинхронный пейджинг с платформой Entity 6.1.3

Я реализовал механизм поискового вызова в соответствии с этой статьей: How to Increase the Performance of Entity Framework with Paging, который, как я думал, был чистым и точным (но не идеальным), но я могу Не забудьте, чтобы это работало асинхронно, что является проблемой.

В соответствии со статьей, я имею создать интерфейс:

public interface IPageList 
{ 
    int TotalCount { get; } 
    int PageCount { get; } 
    int Page { get; } 
    int PageSize { get; } 
} 

Я создал класс:

public class PageList<T> : List<T>, IPageList 
{ 
    public int TotalCount { get; private set; } 
    public int PageCount { get; private set; } 
    public int Page { get; private set; } 
    public int PageSize { get; private set; } 

    public PageList(IQueryable<T> source, int page, int pageSize) 
    { 
     TotalCount = source.Count(); 
     PageCount = GetPageCount(pageSize, TotalCount); 
     Page = page < 1 ? 0 : page - 1; 
     PageSize = pageSize; 
     AddRange(source.Skip(Page * PageSize).Take(PageSize).ToList()); 
    } 

    private int GetPageCount(int pageSize, int totalCount) 
    { 
     if (pageSize == 0) 
      return 0; 

     var remainder = totalCount % pageSize; 
     return (totalCount/pageSize) + (remainder == 0 ? 0 : 1); 
    } 
} 

и, наконец, расширение:

public static class PageListExtensions 
{ 
    public static PageList<T> ToPageList<T>(this IQueryable<T> source, int pageNumber, 
    int pageSize) 
    { 
     return new PageList<T>(source, pageNumber, pageSize); 
    } 
} 

Так что в моих данных слой, у меня есть следующая функция:

public async Task<List<LogEntity>> GetLogsAsync(int pageNumber, int pageSize) 
{ 
    using (_dbContext = new DatabaseContext()) 
    {        
     var results = _dbContext.Logs.Select(l => new 
     { 
      LogId = l.LogId, 
      Message = l.Message, 
     }) 
     .OrderBy(o => o.DateTime) 
     .ToPageList(pageNumber, pageSize).ToList().Select(x => new LogEntity() 
     { 
      LogId = x.LogId, 
      Message = x.Message, 
     }); 

     return await results.AsQueryable<LogEntity>().ToListAsync(); 
    } 
} 

Когда я бегу выше, я получаю:

Additional information: The source IQueryable doesn't implement IDbAsyncEnumerable. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068 .

Я гугле ошибку и в то время как я прочитал множество статей, я все еще изо всех сил, чтобы заставить его работать.

Может ли кто-нибудь сказать мне, как решить эту проблему, поскольку я понятия не имею, с чего начать на этом этапе.

Благодаря

UPDATE-1

Как Иван подчеркнул в своем комментарии, я не думаю, что мне нужно 2 Select, так вот упрощенный вариант:

var results = _dbContext.Logs.OrderBy(o=>o.DateTime) 
    .ToPageList(pageNumber, pageSize).Select(l => new 
{ 
    LogId = l.LogId, 
    Message = l.Message, 
}); 

Все еще не сортирует мою проблему асинхронности. Я в настоящее время, глядя на эту статью, которая, надеюсь, поможет:

How to return empty IQueryable in an async repository method

UPDATE-2

Я думаю, что я понял это, но это еще не так реагировать, как я хотел бы, чтобы это так что я не уверен на 100%, правильно ли это сделано. Я подумал, что, перейдя на вкладку моих журналов в моем приложении WPF, обмен будет мгновенным, но это не так!

Во всяком случае, вот что я изменился:

public async Task<List<LogEntity>> GetLogsAsync(int pageNumber, int pageSize) 
    { 
     using (_dbContext = new DatabaseContext()) 
     { 
      var results = _dbContext.Logs.OrderBy(o=>o.DateTime).ToPageList(pageNumber, pageSize).Select(l => new LogEntity 
      { 
       LogId = l.LogId, 
       Message = l.Message, 
      }).AsAsyncQueryable(); 

      return await results.ToListAsync(); 
     } 
    } 

Если что-нибудь, код, безусловно, проще, чем мой оригинал.

Update-3:

Когда я называю это:

return new PageList<LogEntity>(_dbContext.Logs, pageNumber, pageSize); 

Он возвращает TOTALCOUNT = 100000, PageCount = 200, Page = 0, PageSize 500, но потом выдает ошибку когда AddRange называется т.е.

An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code Additional information: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.

Так я это исправил по телефону:

return new PageList<LogEntity>(_dbContext.Logs.OrderBy(o=>o.DateTime), 
pageNumber, pageSize); 

Когда я попытался назвать @ простейшее предложения krillgar в то

return _dbContext.Logs 
     .Select(l => new LogEntity // Cast here so your .ToPageList 
     { // will start as the object type you want. 
     LogId = l.LogId, 
     Message = l.Message  
     }) 
     .OrderBy(l => l.DateTime) 
     .ToPageList(pageNumber, pageSize); 

Я получаю следующее сообщение об ошибке:

An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code Additional information: The entity or complex type 'MyCompany.DataLayerSql.LogEntity' cannot be constructed in a LINQ to Entities query.

на this.TotalCount = source.Count(); в классе PageList.

Любые идеи?

+0

Похоже, вам не нужен класс 'PagedList', потому что вы его не используете. И в чем причина 2 'Select's? –

+0

@IvanStoev Я использую его. Он используется в строке 10 в функции GetLogsAsync. Что касается выбора 2, это хороший вопрос, и я не хотел задавать здесь несколько вопросов. Я только что попробовал, и я упростил это. Я загружу его через секунду. – Thierry

+1

Не используя его, я имею в виду, что вы не возвращаете 'PagedList ', что является цельной целью этого класса. Если вы хотите получить простой результат, просто включите 'Skip' /' Take' в свой запрос. например 'return await _dbContext.Logs.OrderBy (o => o.DateTime). Выберите (...). Skip ((номер страницы - 1) * pageSize) .Take (pageSize) .ToListAsync();' –

ответ

0

Вы используете async здесь. Если вы не выполняете операции ввода-вывода или очень длинные операции, вы обычно создаете дополнительные накладные расходы, поскольку потоки создаются, управляются и объединяются.

Запрос из базы данных - это операция ввода-вывода, однако вы не узнали, как ведет себя структура Entity Framework, поэтому вам не хватает возможности сделать эту операцию асинхронной.

Entity Framework (и LINQ в целом) использует метод, называемый Deferred Execution. В этом случае это означает, что ничто не отправляется в вашу базу данных, пока вы не захотите действовать с данными. Вы можете условно добавить .Where(), .Skip() и т. Д. К содержимому вашего сердца, и EF будет просто сидеть там, готовясь к построению SQL-запроса.

Чтобы отправить этот оператор SQL в базу данных, вам необходимо выполнить действие, которое вы делаете в своем конструкторе PageList дважды. Первый:

TotalCount = source.Count(); 

Это берет SQL все ваши WHERE заявления и т.д., присоединяет SELECT COUNT (*), и получает результат.

Второй раз здесь:

AddRange(source.Skip(Page * PageSize).Take(PageSize).ToList()); 

В конце вышеуказанной линии, .ToList() пошлет еще один запрос к базе данных, извлекать все столбцы и строки, которые вы задаете для и заполнить все ваши юридические лица. ЭТО - это то, где вы хотите асинхронно, но you can't make an async constructor.

Ваш альтернативный вариант заключается в том, чтобы отказаться от установки всего внутри конструктора и вместо этого использовать метод, который можно легко сделать async.

В оригинальном вопросе, вы начали с этим:

_dbContext.Logs.Select(l => new 
    { 
     LogId = l.LogId, 
     Message = l.Message, 
    }) 
    .OrderBy(o => o.DateTime) 

Вы также с тех пор обновляется, чтобы поставить OrderBy() и .ToPageList() перед вашим .Select(). Тем не менее, вы по-прежнему запрашиваете его как анонимный объект, поэтому вам еще нужно продолжить кастинг после того, как вам нужно.

Возвращаясь к корню вашей проблемы, мы должны смотреть на ваше возвращении заявлении:

return await results.AsQueryable<LogEntity>().ToListAsync(); 

Там нет необходимости делать это, кроме как положить искусственный вызов асинхронного там, который выиграл» т вам ничего (см. выше). Ваш листинг до .AsQueryable<T>() только добавляет к обработке и ничего не дает.

Самый простой способ использовать то, что у вас есть, - это немного переупорядочить и устранить избыточный код. Ваш .ToPageList() уже отбрасывает объект как List<T>, так что если вы делаете вещи в правильном порядке, вы сэкономите много горя:

return _dbContext.Logs 
       .Select(l => new LogEntity // Cast here so your .ToPageList 
           { // will start as the object type you want. 
            LogId = l.LogId, 
            Message = l.Message 
           }) 
       .OrderBy(l => l.DateTime) 
       .ToPageList(pageNumber, pageSize); 

Это действительно все, что вам нужно.

Если вы мертвы, установлен на использовании async, то вы должны переработать свой класс, добавив конструктор по умолчанию, и следующий метод:

public async Task CreateAsync(IQueryable<T> source, int page, int pageSize) 
{ 
    TotalCount = await source.CountAsync(); // async here would help 
    PageCount = GetPageCount(pageSize, TotalCount); 
    Page = page < 1 ? 0 : page - 1; 
    PageSize = pageSize; 
    AddRange(await source.Skip(Page * PageSize) 
         .Take(PageSize) 
         .ToListAsync()); // async here too! 
} 

Это может быть очищен с рефакторинга, но это суть , Затем назовите это так:

// Get your query set up, but don't execute anything on it yet. 
var results = _dbContext.Logs.Select(l => new LogEntity 
            { 
             LogId = l.LogId, 
             l.Message 
            }) 
          .OrderBy(l => l.DateTime); 

var pageList = new PageList<LogEntity>(); 
await pageList.Create(results, pageNumber, pageSize); 

return pageList; 
+0

спасибо за подробный ответ. Извините, что задержка вернулась, но я борюсь с другой частью, которую я опустил из вопроса. Мне нужно преобразовать LogEntity (модель базы данных) в объект журнала (модель домена). Я обновлю, как только выясню эту часть. – Thierry

+0

Когда я попробую ваше «простейшее» предложение, я получаю следующую ошибку: «Сущность или сложный тип« MyCompany.DataLayerSql.LogEntity »не может быть сконструирован в запросе LINQ to Entities.». Я что-то упускаю? – Thierry

+0

Класс имеет конструктор без параметров? Это класс C#? Если вы хотите, чтобы конечный результат был объектом «Log», вместо этого замените его. – krillgar

Смежные вопросы