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

时间:2014-11-08 12:20:52

标签: c# sql sql-server linq

这是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);

文件夹不能嵌套(不受控制),但文件名可以包含斜杠等等,因此可以模拟更深层的文件夹结构

folder.GetFiles 是一个简单的linq equiv(IOrderedQueryable)到select * from files where folderid=@folderid order by Uri

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

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)。但是,如果有人对如何通过一个数据库命中(没有编写存储过程)有任何见解,我很乐意听到它!

kludge的缺点是,如果db命中导致大量记录远远超过该值,那么最终的Take(maxresults)可能会很昂贵。我没有引用的后续Skip(maxresults).Take(1)标记下一页,会伤害两倍。

非常感谢

1 个答案:

答案 0 :(得分:0)

Welp,看起来有必要进行2次数据库命中。我开始注意到调用图将第三个运算符转换为IIF,这使我认为在SQL方面,IIF可能不喜欢子查询作为参数。

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中保留了最大限度来限制流量,所以最糟糕的情况是数据的两倍,我想通过网络。 +1是获取下一个记录,因此可以通知用户从下一页开始的位置。我使用了迭代器模式,所以我不必再循环以获得下一条记录。

相关问题