Сумма интервалов времени с использованием LINQ to Entities

У меня есть группа по вызову LINQ, а затем суммируются значения PossessionTime.Tick как новый временной интервал. Раньше это работало нормально, но теперь мне нужно сохранить коллекцию как IQueryable, а не как список.

PossessionTime = new TimeSpan(x.Sum(p => p.PossessionTime.Ticks))

С этим изменением я теперь получаю следующую ошибку:

В LINQ to Entities поддерживаются только конструкторы и инициализаторы без параметров.

Я изучал использование DbFunctions, но я не думаю, что в нем есть что-то, что могло бы помочь.

Является ли мой единственный вариант переместить список в память и суммировать время там?

IQueryable выглядит следующим образом:

return stats.GroupBy(x => x.Id).OrderByDescending(x => x.Sum(a => a.Kills))
            .Select(x => new WeaponItem
            {         
                PossessionTime = new TimeSpan(x.Sum(p => p.PossessionTime.Ticks))
            });

Мне нужно сохранить его как IQueryable, поскольку я использую его в разбиении на страницы.


person Bad Dub    schedule 02.03.2018    source источник
comment
Внимательно изучите docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/. Проблема, о которой вы упоминаете, вероятно, имеет мало общего с кодом здесь, но с тем фактом, что в это время требуется конкретное значение, а именно сумма, поэтому вся ленивая цепочка оценивается. Может группировка? Я бы не знал без дополнительного кода. Что такое стек вызовов?   -  person Alexandru Clonțea    schedule 02.03.2018
comment
Вам нужно показать IQueryable. Что такое x? Как это определяется?   -  person NetMage    schedule 03.03.2018
comment
Обновлено сейчас @NetMage   -  person Bad Dub    schedule 03.03.2018


Ответы (2)


Я не думаю, что вы сможете сделать это очень легко с Linq-to-SQL/EF, используя прямой linq из-за PossionTime.Ticks. Операции C# DateTime очень ограничены при переводе в операции sql.

Если вашим единственным требованием является поддержка разбивки на страницы, вы можете облегчить себе задачу, выполнив проекцию WeaponItem после операции разбивки на страницы и вытащив страницу в память:

return stats
   .GroupBy(x => x.Id)
   .OrderByDescending(x => x.Sum(a => a.Kills))
   // paginatation
   .Skip(pageNumber * pageSize)
   .Take(pageSize)
   // pull paged result set into memory to make life easy
   .ToList()
   // transform into WeaponItem
   .Select(x => new WeaponItem
   {         
        PossessionTime = new TimeSpan(x.Sum(p => p.PossessionTime.Ticks))
   });

В качестве альтернативы вы можете изменить свой класс WeaponItem для хранения PossesionTimeTicks, а затем предложить PossesionTime в качестве вычисляемого свойства. Это может обойти ограничение:

public class WeaponItem
{
    public long PosseionTimeTicks {get;set;}
    public TimeSpan PossesionTime {get{ return new TimeSpan(PosseionTimeticks); }
}

Тогда я думаю, что вы сможете сохранить свой запрос как IQueryable:

return stats
    .GroupBy(x => x.Id)
    .OrderByDescending(x => x.Sum(a => a.Kills))
    .Select(x => new WeaponItem
    {         
        PossessionTimeTicks = x.Sum(p => p.PossessionTime.Ticks)
    });
person Philip Pittle    schedule 04.03.2018
comment
Думаю второй вариант подойдет, спасибо! - person Bad Dub; 05.03.2018
comment
На самом деле 2-й способ не работает. Я получаю ту же ошибку. указанный член типа «Ticks» не поддерживается в LINQ to Entities. - person Bad Dub; 05.03.2018
comment
Если EF не может перевести DateTime.Ticks, у вас есть несколько вариантов. Вы можете пойти с первой идеей разбивки на страницы, прежде чем делать select. В противном случае может быть способ добавить плагин в EF, который сообщает ему, как перевести DateTime.Ticks в sql. Или вы можете обновить свою базу данных, чтобы всегда хранить PossesionTime в виде тиков, а не в DateTime. - person Philip Pittle; 05.03.2018
comment
Да, хранение в виде тиков звучит как единственный вариант, так как мне нужно иметь возможность сортировать запрос и разбивать его на страницы. - person Bad Dub; 05.03.2018

Вы можете создать TimeSpan из HMS, используя DBFunctions.CreateTime, чтобы вы могли выполнить математику для преобразования:

return stats.GroupBy(x => x.Id).OrderByDescending(x => x.Sum(a => a.Kills))
            .Select(x => new WeaponItem
            {         
                PossessionTime = DBFunctions.CreateTime(0, 0, x.Sum(p => DBFunctions.DiffSeconds(p.PossessionTime, TimeSpan.Zero)))
            });

Примечание. Я предполагаю, что CreateTime примет аргумент секунд >= 60 секунд, в противном случае вам нужно будет преобразовать сумму в часы, минуты и секунды.

person NetMage    schedule 05.03.2018
comment
Я не могу сделать .Ticks, потому что это IQueryable, идущий к сущностям. Член указанного типа «Ticks» не поддерживается в LINQ to Entities. - person Bad Dub; 06.03.2018
comment
Каков тип PossessionTime в базе данных и EF? Я предполагал, что вы могли бы немного Ticks, поскольку вы приняли ответ, в котором использовалось Ticks... но теперь я вижу, что это не сработало. - person NetMage; 06.03.2018
comment
Его временной интервал в базе данных, но мне, возможно, придется изменить его сейчас. - person Bad Dub; 06.03.2018
comment
Я изменил выражение, чтобы использовать DiffSeconds для вычисления секунд, предполагая, что вы можете передать константу TimeSpan.Zero в SQL. - person NetMage; 06.03.2018