Моя собственная функция упорядочивания

#c# #.net #sql #linq #entity-framework

#c# #.net #sql #linq #entity-framework

Вопрос:

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

Для этого я использовал стандартный интерфейс IComparer и написал свою собственную функцию сравнения, которая сравнивает две фотографии. Проблема в том, что я делаю так, что мне приходится сначала загружать список всех фотографий из базы данных. Это кажется большим количеством ненужных усилий, которых я хотел бы избежать. Итак, мне интересно, возможно ли создать мою собственную функцию SQL, которая будет выполнять сравнение на стороне базы данных и возвращает мне только те фотографии, которые я хочу? Это эффективнее, чем сравнивать все фотографии на стороне сервера?

Код для моего собственного средства сравнения:

 public class PictureComparer : IComparer<Picture>
{
    public int Compare(Picture p1, Picture p2)
    {
        double firstPictureScore = (((double)p1.PositiveVotes/(double)(p1.PositiveVotes p1.NegativeVotes))*100);
        double secondPictureScore = (((double)p2.PositiveVotes / (double)(p2.PositiveVotes   p2.NegativeVotes)) * 100);
        if (firstPictureScore < secondPictureScore) return 1;
        if (firstPictureScore > secondPictureScore) return -1;
        return 0;
    }
}
  

И код, который использует comaprer:

  var pictures = db.Pictures.Include(q => q.Tags).Include(q => q.User).ToList();
 pictures = pictures.OrderBy(q => q, new PictureComparer()).Skip(0 * 10).Take(10).ToList();
  

Комментарии:

1. непосредственно перед тем, как подумать над ответом на ваш вопрос, в вашем методе PictureComparer. Compare() вы можете забыть о * 100 при вычислении firstPictureScore и secondPictureScore. Это ничего не меняет в сравнении

2. По какой-либо причине вы не можете закодировать хранимую процедуру, которая будет возвращать только нужные записи, и вызвать ее из EF?

3. и что вы подразумеваете под «фотографиями, которые я хочу»? Первые X из них?

4. @PierrOz, да, ты прав, я могу опустить «* 100», а под первыми X фотографиями я подразумеваю, что я использую функции «.Skip(0 * 10).Take(10)», чтобы сделать только 10 фотографий с выбранной позиции в списке, вот почему я не хочу загружать все фотографии из базы данных и сортировать их.

5. @Oded, возможно, я смогу, но это не то, что я когда-либо делал, вот почему я прошу наилучшее решение для такого сценария, наиболее эффективное и простое в создании.

Ответ №1:

Удалите первый вызов ToList и используйте лямбда-выражение вместо определения средства сравнения:

 var result = db.Pictures
    .Include(q => q.Tags)
    .Include(q => q.User)
    .OrderByDescending(q => 
         q.PositiveVotes   q.NegativeVotes == 0
             ? -1
             : q.PositiveVotes / (double)(q.PositiveVotes   q.NegativeVotes))
    .Skip(n * 10)
    .Take(10)
    .ToList();
  

Комментарии:

1. Вау, это было быстро. Это выглядит как отличный фрагмент кода, и, насколько я понимаю, сортировка выполняется на стороне базы данных, верно? Но у меня есть одна проблема с этим кодом, я не хочу исключать элементы, которые имеют 0 положительных или отрицательных голосов, я просто хочу, чтобы они были в конце списка, возможно ли этого добиться?

Ответ №2:

Вычисления в вашем коде компаратора независимы (т. е. сравнение зависит только от упорядочивания значения, которое может быть вычислено без ссылки на элемент, с которым вы сравниваете). Поэтому вы должны сначала вычислить свое положительное процентное число и просто использовать вычисленное значение в вашем компараторе.

Это, безусловно, должно быть сделано в базе данных, если это возможно (т. Е. если у вас есть доступ для внесения изменений в базу данных). Базы данных подходят для такого рода вычислений, и вы, вероятно, могли бы выполнять это на лету без необходимости кэшировать вычисленные значения, под чем я подразумеваю представление, которое вычисляет процент для вас, а не предварительное вычисление и сохранение значения каждый раз при положительном или отрицательном голосовании. Это избавит от необходимости загружать все фотографии для сравнения, так как вы можете просто упорядочить их по положительному проценту. Ниже приведен некоторый образец sql, который выполнит эту работу (обратите внимание, что это всего лишь образец…возможно, вы захотите сохранить голосование как бит или что-то более эффективное). Таблица голосов содержит список всех голосов за конкретную картинку и тех, кто за нее проголосовал.

 declare @votes table(
pictureId int,
voterId int,
vote int)

insert into @votes select 1,1,1
insert into @votes select 1,2,-1
insert into @votes select 1,3,1
insert into @votes select 1,4,1
insert into @votes select 2,1,-1
insert into @votes select 2,2,-1
insert into @votes select 2,3,1
insert into @votes select 2,4,1

declare @votesView table(
pictureId int,
positiveVotes int,
NegativeVotes int)

insert into @votesView
select pictureId, sum(case when vote > 0 then 1 else 0 end) as PositiveVotes, 
SUM(case when vote < 0 then 1 else 0 end) as NegativeVotes from @votes group by pictureId

select pictureId, convert(decimal(6,2),positiveVotes) / convert(decimal(6,2), (positiveVotes   negativeVotes)) as rating from @votesView
  

Комментарии:

1. Неплохо, так что, насколько я понимаю, будет лучше всегда сохранять значение «positivePercentage» и вычислять его в момент добавления нового голосования?

2. Это один из способов, но, вероятно, не требуется. Просто имейте представление, которое поддерживает вычисления. Самым простым способом было бы создать два представления … первое суммирует положительные и отрицательные голоса (согласно предпоследнему оператору select в моем коде выше), а второе представление ссылается на это представление и вычисляет процентное значение (согласно последнему оператору select выше). Таким образом, вам не нужно ничего делать, когда кто-то голосует, поскольку просмотры всегда будут актуальными.