NHibernate - 使用COUNT(DISTINCT)查询Linq

时间:2011-03-07 17:51:13

标签: nhibernate linq-to-nhibernate

我正在尝试使用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

第二个查询的问题是外部查询中不存在“distinct”关键字,只有内部查询。任何想法如何纠正这个?

感谢您的任何建议!

[UPDATE]

这是构建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 个答案:

答案 0 :(得分:1)

似乎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();

好吧,我希望你不要太困惑。