NHibernate - запрос Linq с использованием COUNT (DISTINCT) - PullRequest
0 голосов
/ 07 марта 2011

Я пытаюсь заставить постраничный запрос работать правильно, используя LINQ и NHibernate.На одном столе это прекрасно работает, но когда объединено более одного стола, это вызывает у меня хаос.Вот что у меня есть.

public virtual PagedList<Provider> GetPagedProviders(int startIndex, int count, System.Linq.Expressions.Expression<Func<Record, bool>> predicate) {

    var firstResult = startIndex == 1 ? 0 : (startIndex - 1) * count;

    var query = (from p in session.Query<Record>()
                .Where(predicate)
                select p.Provider);

    var rowCount = query.Select(x => x.Id).Distinct().Count();

    var pageOfItems = query.Distinct().Skip(firstResult).Take(count).ToList<Provider>();
    return new PagedList<Provider>(pageOfItems, startIndex, count, rowCount);
} 

У меня есть пара проблем с этим.Прежде всего, переменная rowCount возвращает значение COUNT (*) в соединении, которое имеет один ко многим.Так что мой счет далеко от того, что должно быть, я ожидаю 129, но возвращаюсь за 1K.

Вторая проблема, с которой я сталкиваюсь, связана с результатами.Если я на первой странице (firstResult = 0), то я получаю правильные результаты.Однако, если firstResult отличается от 0, он создает совершенно другой SQL.Ниже приведен SQL-код, сгенерированный в обоих сценариях, я обрезал некоторые жирности, чтобы сделать его немного более читабельным.

--firstResult = 0
select distinct TOP (30) provider1_.Id as Id12_, provider1_.TaxID as TaxID12_, 
provider1_.Facility_Name as Facility5_12_, provider1_.Last_name as Last6_12_, 
provider1_.First_name as First7_12_, provider1_.MI as MI12_ 
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id, 
PPOProviders provider2_ where record0_.Provider_id=provider2_.Id 
and provider2_.TaxID='000000000'

--firstResult = 30
SELECT TOP (30) Id12_, TaxID12_, Facility5_12_, Last6_12_, First7_12_, MI12_, 
FROM (select distinct provider1_.Id as Id12_, provider1_.TaxID as TaxID12_, 
provider1_.Facility_Name as Facility5_12_, 
provider1_.Last_name as Last6_12_, 
provider1_.First_name as First7_12_, 
provider1_.MI as MI12_, 
ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row 
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id, 
PPOProviders provider2_ 
where record0_.Provider_id=provider2_.Id and provider2_.TaxID='000000000') as query 
WHERE query.__hibernate_sort_row > 30 
ORDER BY query.__hibernate_sort_row

Проблема со вторым запросом в том, что ключевое слово «Different» отсутствуетна внешний запрос только внутренний запрос.Есть идеи как это исправить?

Спасибо за любые предложения!

[ОБНОВЛЕНИЕ]

Это код, который создает предикат запроса Linq.

private Expression<Func<Record, bool>> ParseQueryExpression(string Query) {
            Expression<Func<Record, bool>> mExpression = x => true;
            string[] splitQuery = Query.Split('|');
            foreach (string query in splitQuery) {
                if (string.IsNullOrEmpty(query))
                    continue;
                int valStartIndex = query.IndexOf('(');
                string variable = query.Substring(0, valStartIndex);
                string value = query.Substring(valStartIndex + 1, query.IndexOf(')') - valStartIndex - 1);

                switch (variable) {
                    case "tax":
                        mExpression = x => x.Provider.TaxID == value;
                        break;
                    case "net":
                        mExpression = Combine<Record>(mExpression, x => x.Network.Id == int.Parse(value));
                        break;
                    case "con":
                        mExpression = Combine<Record>(mExpression, x => x.Contract.Id == int.Parse(value));
                        break;
                    case "eff":
                        mExpression = Combine<Record>(mExpression, x => x.Effective_Date >= DateTime.Parse(value));
                        break;
                    case "trm":
                        mExpression = Combine<Record>(mExpression, x => x.Term_Date <= DateTime.Parse(value));
                        break;
                    case "pid":
                        mExpression = Combine<Record>(mExpression, x => x.Provider.Id == long.Parse(value));
                        break;
                    case "rid":
                        mExpression = Combine<Record>(mExpression, x => x.Rate.Id == int.Parse(value));
                        break;                        
                }
            }
            return mExpression;
        }

1 Ответ

1 голос
/ 08 марта 2011

Кажется, что у провайдера Linq есть какое-то "неожиданное" поведение там. Я мог бы воспроизвести проблему с rowCount, который вы описываете, а также столкнулся с некоторыми проблемами при попытке исправить ее с помощью подзапроса.

Если я правильно понимаю ваши намерения, вы, по сути, хотите иметь постраничный список поставщиков, записи которых соответствуют определенным критериям. В этом случае я бы предложил использовать подзапрос. Однако я попытался реализовать подзапрос с помощью метода Query(), но он не сработал. Поэтому я попробовал метод QueryOver(), который работал безупречно. В вашем случае желаемые запросы будут выглядеть так:

Provider pAlias = null;
var query = session.QueryOver<Provider>(() => pAlias).WithSubquery
                        .WhereExists(QueryOver.Of<Record>()
                            .Where(predicate)
                            .And(r => r.Provider.Id == pAlias.Id)
                            .Select(r => r.Provider));

var rowCount = query.RowCount();

var pageOfItems = query.Skip(firstResult).Take(count).List<Provider>();

Таким образом, вам не нужно бороться с Distinct(). И насколько мне лично нравится и пользуюсь NHibernate.Linq, я полагаю, если он не работает в данной ситуации, нужно использовать что-то еще, что работает.

Редактировать: Разделение запроса на более мелкие единицы.

// the method ParseQueryExpression() does not need to be modified, the Expression should work like that
System.Linq.Expressions.Expression<Func<Record, bool>> predicate = x => x.Provider.TaxID == "000000000";

// Query to evaluate predicate
IQueryable<Record> queryId = session.Query<Record>().Where(predicate).Select(r => r.Provider);
// extracting the IDs to use in a subquery
List<int> idList = queryId.Select(p => p.Id).Distinct().ToList();
// total count of distinct Providers
int rowCount = idList.Count;

// new Query on Provider, using the distinct Ids
var query = session.Query<Provider>().Where(p => idList.Contains(p.Id));
// the List<Provider> to display on the page
var pageOfItems = query.Skip(firstResult).Take(count).ToList();

Проблема здесь в том, что у вас есть несколько запросов и, следовательно, несколько обращений к БД. Другая проблема возникает при изучении созданного SQL. Подзапрос idList.Contains(p.Id) сгенерирует предложение с большим количеством параметров.

Как видите, это запросы NHibernate.Linq. Это связано с тем, что новая функция QueryOver не является истинным поставщиком Linq и плохо работает с динамическими выражениями Linq.

Вы можете обойти это ограничение, используя Detached QueryOvers. Недостатком здесь является то, что вам придется как-то изменить свой ParseQueryExpression(), например, вот так:

private QueryOver<Record> ParseQueryExpression(string Query)
{
    Record rAlias = null;
    var detachedQueryOver = QueryOver.Of<Record>(() => rAlias)
                .JoinQueryOver(r => r.Provider)
                .Where(() => rAlias.Provider.TaxID == "000000000")
                .Select(x => x.Id);
    // modify your method to match the return value
    return detachedQueryOver;
}

// in your main method
var detachedQueryOver = QueryOver.Of<Record>()
    .WithSubquery
    .WhereProperty(r => r.Id
    .In(this.ParseQueryExpression(...));

var queryOverList = session.QueryOver<Provider>()
    .WithSubquery
    .WhereProperty(x => x.Id)
    .In(detachedQueryOver.Select(r => r.Provider.Id));

int rowCount = queryOverList.RowCount();
var pageOfItems = queryOverList.Skip(firstResult).Take(count).List();

Что ж, надеюсь, вас это не смущает.

...