LINQ to SQL, как я могу получить количество поисковых терминов, найденных в поле (ах)? - PullRequest
2 голосов
/ 02 декабря 2010

Есть ли способ написать запрос в LINQ, чтобы вернуть количество поисковых терминов, найденных в поле (ах)

В принципе, я хочу, чтобы это работало:

var matches = from t in _db.Books
                          let score = GetScore(t, searchterms)
                          where score >= 1
                          orderby score descending
                          select t;

public static int GetScore(Book b, params string[] searchterms)
    {
        int count = 0;
        foreach (string term in searchterms)
        {
            if (b.Title.Contains(term))
                count++;
        }
        return count;
    }

Но, конечно, это не может работать.Можно ли перевести мою маленькую функцию GetScore в LINQ?

Спасибо.

EDIT : Я также предпочел бы, чтобы счет был доступен.В идеале я буду выбирать свои результаты в классе SearchResults (для представления), который будет содержать некоторую информацию о книге и оценку книги по запросу.Чтобы обновить мой запрос, это было бы что-то вроде этого:

var matches = from t in _db.Books
                          let score = GetScore(t, searchterms)
                          where score >= 1
                          orderby score descending
                          select new SearchResult
                                                {
                                                    Title = t.Title,
                                                    Type = "Book",
                                                    Link = "Books/Details/" + t.BookID,
                                                    Score = score
                                                };

Извините, я изначально не был более ясен.

Ответы [ 6 ]

1 голос
/ 02 декабря 2010

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

var terms = new [] { "s", "t", "r", "e", "b", "c", };

var ids =
    from term in terms
    from id in _db.Books
        .Where(book => book.Title.Contains(term))
        .Select(book => book.Id)
    group term by id into gts
    orderby gts.Count() descending
    select gts.Key;

var selectedIds = ids.Take(50).ToArray();

var query =
    from book in _db.Books
    where selectedIds.Contains(book.Id)
    select book;

Я написал ids, чтобы вернуть список идентификаторов, отсортированных по тем, которые сначала соответствуют большинству терминов.Это было наиболее близко к тому же результату, который вы хотели получить в своем вопросе.Затем я решил использовать Take(50), чтобы получить 50 лучших результатов.Очевидно, что вы можете изменить эту стратегию в соответствии со своими потребностями, но в конечном итоге вы должны получить массив идентификаторов для использования в конечном запросе.

Надеюсь, это поможет.


РЕДАКТИРОВАТЬ: на основе редактирования ОП.

Вот как выполнить запрос с включенным счетом:

var terms = new [] { "s", "t", "r", "e", "b", "c", "l", "i", };

var idScores =
    from term in terms
    from id in _db.Books
        .Where(book => book.Title.Contains(term))
        .Select(book => book.BookID)
    group term by id into gts
    select new
    {
        Id = gts.Key,
        Score = gts.Count(),
    };

var selectedIds = idScores.Select(x => x.Id).Take(50).ToArray();

var selectedBooks =
    from book in _db.Books
    where selectedIds.Contains(book.BookID)
    select book;

var query =
    from b in selectedBooks.ToArray()
    join x in idScores on b.BookID equals x.Id
    orderby x.Score descending
    select new
    {
        Title = b.Title,
        Type = "Book",
        Link = "Books/Details/" + b.BookID,
        Score = x.Score,
    };
1 голос
/ 02 декабря 2010

Если вы хотите преобразовать функцию GetScore () в LINQ, вы можете изменить весь LINQ следующим образом:

               var matches = from t in _db.Books
                      where searchterms.Count(c => c == t.Title) >= 1
                      orderby searchterms.Count(c => c == t.Title)
                      select t;

Теперь он будет успешно скомпилирован, но во время выполнения, когда вы свяжете эти совпадения с сеткой.или любое другое, где будет выдано исключение. «Локальная последовательность не может быть использована в реализации операторов запросов LINQ to SQL, кроме оператора Contains ()».таблица и список в памяти. Поскольку вы пишете запрос к таблице SQL, он проходит через LINQ to SQL, который справедливо жалуется, что не может этого сделать, поэтому, если вы действительно хотите выполнить соединение в памяти, то вы должныиспользуйте _db.Books.AsEnumerable ()

, тогда запрос будет:

      var matches = from t in _db.Books.AsEnumerable()
                      where searchterms.Count(c => c == t.Title) >= 1
                      orderby searchterms.Count(c => c == t.Title)
                      select t;
0 голосов
/ 03 декабря 2010

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

давайте начнем с конца. Я не могу заставить Expression возвращать анонимный тип, поэтому я создаю общий, который планирую использовать как результат

public class GRes<T>
{
    public T Key { get; set; }
    public int Count { get; set; }
}

Я собираюсь выполнить группировку по любому полю объекта или самого объекта, а затем по результатам этой группировки вызовем Sum fnction с некоторой лямбдой, которая выглядит следующим образом

x=>0 + IIF(x.Code.Contains(p1), 1, 0) + IIF(x.Code.Contains(p2), 1, 0) ...

где Код - это какое-то поле, а p1, p2 ... - ваши условия поиска на последнем звонке будет

IQueriable<GRes<Book>> result = context.Books
.GroupBy(d => d).CountIn(searchTerms, "Code")
.Where(r => r.Count > 0)
.OrderByDescending(r => r.Count);

CountIn - это расширение:

public static IQueryable<GRes<TKey>> CountIn<TKey, TValue>(this IQueryable<IGrouping<TKey, TValue>> source, IEnumerable<string> values, Expression<Func<TValue,string>> selector)
    {
        ParameterExpression xExpr = selector.Parameters[0];
        Expression propExpr = selector.Body;
        MethodInfo mi = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
        Expression res = Expression.Constant(0);            
        foreach (string term in values)
        {
            Expression value = Expression.Constant(term);
            MethodCallExpression methodEpr = Expression.Call(propExpr, mi,value);
            Expression tx = Expression.Condition(methodEpr, Expression.Constant(1), Expression.Constant(0));
            res = Expression.Add(res, tx);
        }
        var r0 = Expression.Lambda<Func<pp_Disease, int>>(res, xExpr);

        Type groupingType = typeof(IGrouping<TKey, TValue>);
        ParameterExpression selPar = Expression.Parameter(groupingType, "i");
        MethodInfo mi1 = typeof(Enumerable).GetMethods()
            .FirstOrDefault(m => m.Name == "Sum" 
                && m.ReturnParameter.ParameterType == typeof(int)
                && m.GetParameters().Count() == 2)
            .MakeGenericMethod(typeof(pp_Disease));

        Expression r1 = Expression.MemberInit(Expression.New(typeof(GRes<TKey>))
            , Expression.Bind(typeof(GRes<TKey>).GetMember("Count")[0], Expression.Call(mi1, selPar, r0))
            , Expression.Bind(typeof(GRes<TKey>).GetMember("Key")[0], Expression.Property(selPar, "Key")));
        return source.Select(Expression.Lambda<Func<IGrouping<TKey, TValue>, GRes<TKey>>>(r1, selPar));
    }

и когда эта функция будет вызвана, вы получите SQL следующим образом:

SELECT 
...
FROM ( SELECT 
    ...
    FROM ( SELECT 
        .... 
        SUM([Extent1].[A1]) AS [A1]
        FROM ( SELECT 
            ...
            0 + (CASE WHEN ([Extent1].[Code] LIKE N'%2%') THEN 1 ELSE 0 END) + (CASE WHEN ([Extent1].[Code] LIKE N'%I%') THEN 1 ELSE 0 END) AS [A1]
            FROM (SELECT 
      ...
      FROM [dbo].[pp_Disease] AS [pp_Disease]) AS [Extent1]
        )  AS [Extent1]
        GROUP BY [K1], [K2], [K3], [K4]
    )  AS [GroupBy1]
    WHERE [GroupBy1].[A1] > 0
)  AS [Project1]
ORDER BY [Project1].[C1] DESC

я удалил некоторые объявления полей (EF сгенерировал sql огромный), наиболее важным для этого примера является строка с параметрами, которые мы поместили в нашу функцию

0 голосов
/ 03 декабря 2010

У вас есть два варианта:

  • Выполнить поиск в БД с помощью хранимой процедуры, которая возвращает пару (BookId, Score), а затем использовать ее для выполнения запроса в LINQ2SQL
  • Используйте ToList () для выполнения запроса и избегайте получения ошибки «Локальная последовательность не может быть использована ...».

Для второго варианта запрос (в синтаксисе лямбда-выражения) будетбыть чем-то вроде

db.Books
    .ToList()
    .Select(t=> new SearchResult {
                                     Title = t.Title,
                                     Type = "Book",
                                     Link = "Books/Details/" + t.BookID,
                                     Score = GetScore(t, searchTerms)
            })
    .Where(t => t.Score >=1);

В последнем случае вы перенесете всю таблицу Book в память (и будете использовать объекты LINQ2Object для фильтрации), поэтому я бы предпочел перейти на первый.

0 голосов
/ 02 декабря 2010
var matches = from t in _db.Books
                          let score = searchterms.Where(term => t.Title.Contains(term)).Count()
                          where score >= 1
                          orderby score descending
                          select t;

редактировать

Если его LINQ к SQL, я думаю, что мое решение делится на 2 части

// just results in db
    string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
    var results = from t in _db.Books
                  where terms.Contains(t.Title.Contains)
                  select t;

// sort results in c#
    var sorting = for entry in results
                  let score = searchterms.Where(term => entry.Title.Contains(term)).Count()
                  orderby score
                  select new {......};

редактировать

оооо извините маленькая ошибка. по первому запросу

string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
    var results = from t in _db.Books
                  where terms.Contains(t.Title.Contains)
                  select t;

изменить на

string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
    var results = from t in _db.Books
                  where terms.Contains("^" + t.Title + "^")
                  select t;

причина, по которой я использую этот запрос, заключается в том, что 'var results' даст весь результат из db, который должен быть в порядке, потому что это строка. Содержит. Затем отсортируйте результат в следующем запросе.

0 голосов
/ 02 декабря 2010
        i took your problem as below

        books.Add("Robin HOOD");
        books.Add("Charles");
        books.Add("James");

        search.Add("Rob");
        search.Add("ood");
        search.Add("les");
        search.Add("am");

      so you want the count of search items which are found in any of the books so if run this example , you will get the  correct result.

      var temp = searchterms.Where(x=>books.Where(y=>y.Title.Contains(x)).Count()>0);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...