Linq2SQL在同一查询中进行分组和取消分组

本文关键字:取消 查询 Linq2SQL | 更新日期: 2023-09-27 18:29:50

以下是LINQ to SQL中的一个难题:

string p = prefix ?? "";
string d = delimiter ?? "";
var filegroups = from b in folder.GetFiles(data)
                    where b.Uri.StartsWith(p) && b.Uri.CompareTo(marker ?? "") >= 0
                    group b by data.DataContext.GetFileFolder(p, d, b.Uri);
//var folders = from g in filegroups where g.Key.Length > 0 select g;
//var files = from g in filegroups where g.Key.Length == 0 select g;
var files = filegroups.SelectMany(g => g.Key.Length > 0
    ? from b in g.Take(1) select new FilePrefix { Name = g.Key }
    : from b in g select new FilePrefix { Name = b.Uri, Original = b });
var retval = files.Take(maxresults);

文件夹不能嵌套(超出我的控制),但文件名可以包含斜线,因此可以模拟更深层次的文件夹结构

文件夹。GetFilesselect * from files where folderid=@folderid order by Uri 的一个简单linq equiv(IOrderedQueryable)

前缀是一个过滤器,表示只返回以…开头的文件
分隔符是路径分隔符,如"/"
标记用于分页-在指定点开始返回

data.DataContext.GetFileFolder映射到sql标量函数:返回整个字符串,直到并包括前缀字符串后面的下一个分隔符RETURN substring(@uri, 0, charindex(@delimiter, @uri, len(@prefix)) + len(@delimiter))这是为了排除故障——原来是一个客户端where子句,它确实正确地映射到了TSQL。我只是希望做一个函数能改变最终图形中的内容,但没有。

在上面的例子中,文件组、注释掉的文件夹和文件都按预期工作

目标是只访问数据库一次。我想在一个返回中,根据FilePrefix对象的解释显示子文件夹和文件(文件夹有一个空的"原始"值)

问题在于最后的selectmany抛出"无法将节点‘ClientQuery’格式化为SQL执行"

我强烈怀疑,如果没有TSQL翻译,这将非常有效,但从逻辑上看,为什么它不做数据库工作,然后选择FilePrefixes客户端作为最后一步?

太晚了;)但明天,我将通过在数据库上滑动ToList()或类似的东西来恢复对数据库的双击,以使最后一步是完全客户端(kludge)。但是,如果有人对如何通过一次数据库访问(除了编写存储过程)实现这一点有任何见解,我很乐意听到!!

这种拼凑的缺点是,如果数据库命中导致的记录数量远远超过这个数字,那么最终的Take(maxresults)可能会很昂贵。而随后我没有引用的Skip(maxresults).Take(1),用于标记下一页,会造成双倍的伤害。

非常感谢

Linq2SQL在同一查询中进行分组和取消分组

好吧,看起来需要2次数据库命中。我首先注意到调用图将第三运算符转换为IIF,这让我认为IIF在sql方面可能不喜欢将子查询作为参数。

string p = prefix ?? "";
string d = delimiter ?? "";
var filegroups = from b in folder.GetFiles(data)
                    where b.Uri.StartsWith(p) && b.Uri.CompareTo(marker ?? "") >= 0
                    group b by data.DataContext.nx_GetFileFolder(p, d, b.Uri);
var folders = from g in filegroups where g.Key.Length > 0 select g.Key;
var files = from b in folder.GetFiles(data)
            where b.Uri.StartsWith(p) && b.Uri.CompareTo(marker ?? "") >= 0
                && data.DataContext.nx_GetFileFolder(p, d, b.Uri).Length == 0
            select b;
folders = folders.OrderBy(f => f).Take(maxresults + 1);
files = files.OrderBy(f => f.Uri).Take(maxresults + 1);
var retval = folders.AsEnumerable().Select(f => new FilePrefix { Name = f })
            .Concat(files.AsEnumerable().Select(f => new FilePrefix { Name = f.Uri, Original = f }))
            .OrderBy(b => b.Name).Take(maxresults + 1);
int count = 0;
foreach (var bp in retval)
{
    if (count++ < maxresults)
        yield return bp;
    else
        newmarker.Name = bp.Name;
}
yield break;

有点不那么优雅。。。我留下了文件组和文件夹,但重写了文件查询以删除该组(生成了更干净的sql,可能更高效)。

Concat在这种新方法中仍然给我带来了麻烦,所以我引入了AsEnumerable调用,这就是将其分解为对数据库的2次命中的要点。

我在sql中保留了maxresults来限制流量,所以在最坏的情况下,数据量是我想要的两倍。+1是为了获得下一条记录,这样用户就可以被通知在下一页的哪里开始。我使用了迭代器模式,这样我就不必再次循环来获得下一条记录。