Linq2Sql数据检索的要点

3
我是一名有帮助的助手,可以为您翻译。
我目前正在处理一个使用linq2sql作为其数据库访问框架的项目,现在有很多linq查询基本上都是做以下事情:
var result =    from <some_table>
                join <some_other_table>
                join <another_table>
                select <some_other_domain_model> // This is a non linq2SQL poco

return result.Where(<Some_Predicate>);

例如假设您读取了3个表,然后将内容汇总为一个大型的高级模型,以便发送到视图。忽略域的混合,因为这不会让我太困扰,重点在于最终的where子句。
我之前没有使用过Linq2Sql,所以我的理解是:
1. 基于from、join、join、select linq生成SQL语句 2. 检索所有行 3. 将所有数据映射到一个大的模型中(在内存中) 4. 循环遍历所有模型,然后只返回适用的模型
这是我的问题核心,如果上述流程是正确的,那么这个问题在我的想法中就有意义。但是,有些人认为比我更了解这个框架,他们争论第4步已经被整合到SQL生成中,因此它不会拉回所有记录,但我不知道它是如何做到的,因为它需要所有数据来填充其中一个单独的where子句,所以我认为在第4步时,所有行都已经被读取并且已经在内存中。
我正在努力推动他们将where子句移动到Linq中,以便在数据库层面上过滤掉不需要的记录,但我想知道是否有人能够建议我上述的假设是否正确?
编辑:
已添加注释以更加关注事实,即 不是由Linq2Sql生成的对象,而是在其他地方手动编写的随机poco,在问题的上下文中突出我的主要焦点。因为问题不在于“我把where子句放在哪里有没有关系”,而是在于“当应用于从Linq2Sql查询生成的非Linq2Sql对象时,是否仍然将where子句纳入基础查询中”。以下是我希望更简洁明了的例子,以便更好地说明我的疑问所在:
/*
    I am only going to put auto properties into the linq2sql entities,
    although in the real world they would be a mix of private backing
    fields with public properties doing the notiftying.
*/

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.some_table_1")]
public class SomeLinq2SqlTable1
{
    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="some_table_1_id", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
    public int Id {get;set;}
}

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.some_table_2")]
public class SomeLinq2SqlTable2
{
    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="some_table_2_id", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
    public int Id {get;set;}

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="some_table_2_name", AutoSync=AutoSync.OnInsert, DbType="Varchar NOT NULL", IsPrimaryKey=false)]
    public string Name {get;set;}
}

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.some_table_3")]
public class SomeLinq2SqlTable3
{
    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="some_table_3_id", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
    public int Id {get;set;}

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="some_table_3_other", AutoSync=AutoSync.OnInsert, DbType="Varchar NOT NULL", IsPrimaryKey=false)]
    public string Other {get;set;}
}

/*
    This is some hand rolled Poco, has NOTHING to do with Linq2Sql, think of it as 
    a view model of sorts.
*/
public class SomeViewModel
{
    public int Id {get;set;}
    public string Name {get;set;}
    public string Other {get;set;}
}

/*
    Here is psudo query to join all tables, then populate the
    viewmodel item from the query and finally do a where clause
    on the viewmodel objects.
*/
var result =    from // Linq2SqlTable1 as t1
                join // Linq2SqlTable2.id on Linq2SqlTable1.id as t2
                join // Linq2SqlTable3.id on Linq2SqlTable1.id as t3
                select new ViewModel { Id = t1.Id, Name = t2.Name, Other = t3.Other }

return result.Where(viewModel => viewModel.Name.Contains("some-guff"));

考虑上面的例子,最终的Where语句是否会被纳入基础查询中?还是viewModel上的where会导致检索并在内存中评估?

对于这个问题的描述可能有些啰嗦,但是关于它的文档非常少,而且这是一个非常具体的问题。

3个回答

5
您不需要将Where子句推得更高。只要resultIQueryable<T>(对于某个T),它就可以放在原地。LINQ是可组合的。事实上,使用LINQ语法和扩展方法语法之间绝对没有任何区别,并且二者的作用完全相同。基本上,当您创建一个查询时,它只是构建了一个被请求的模型。直到您开始遍历它 (foreach, ToList(), 等) 之前,什么都没有被执行。因此,在结尾添加额外的Where是可以的:那将被构建成组合查询。
您可以通过监视SQL连接来验证这一点;您会发现它在TSQL中包括where子句,并在SQL服务器上进行过滤。
这允许出现一些有趣的情况,例如灵活的搜索:
IQueryable<Customer> query = db.Customers;
if(name != null) query = query.Where(x => x.Name == name);
if(region != null) query = query.Where(x => x.Region == region);
...
if(dob != null) query = query.Where(x => x.DoB == dob);
var results = query.Take(50).ToList();

关于您的假设,它们是不正确的 - 实际上是这样的:
1.构建可组合查询,从join、join和select中分别组合。 2.进一步组合查询,添加一个where条件(与上述组合方式没有区别)。 3.在稍后的某个时候,迭代查询 a.从完全组合的查询生成sql语句 b.检索行 c.映射到模型 d.产生结果
注意,SQL生成只有在迭代查询时才会发生;在那之前,您可以整天保持组合状态。直到迭代查询时,它才会接触SQL服务器。

好的,所以它们都是惰性加载的,这很有道理。但我不明白的是,假设有100行数据,我要将这3个连接表的结果放入某个随机模型中,例如 Select new RandomModel { id = table1.Id, name = table2.Name, other = table3.data} 然后应用一个 where 子句,比如 randomModel => randomModel.Name.Contains("foo"); 那么它只能通过拉回所有100行数据,组成列表,然后在内存中逐一评估每个对象,或者拉回每个单独的行来创建对象,然后在内存中执行 where 子句。 - Grofit
@Grofit 在原始LINQ中包含where和单独应用Where之间绝对没有任何区别。它可以将where组成SQL。LINQ语法只是对基础的.Where、.Join等的方便封装。所有的LINQ查询都是由多个单独的调用组成的。系统甚至无法检测到Where是后来添加的,还是在原始LINQ中添加的。简而言之:它可以把where放进TSQL。 - Marc Gravell
@Grofit,你尝试过简单地对生成的 SQL 进行分析吗?可以使用 sql-trace 或者只需设置 db.Log = Console.Out;。这是好的。它实际上并不会为每一行创建一个 SomeViewModel - 它只是跟踪视图模型 Namet2.Name 相同,并使用它。如果你看看 joinlet 等在幕后的工作方式,就会明白这个问题变得微不足道的原因:实际上,有很多隐藏的更广泛的类型被生成来表示组合的连接模型(在规范中称为“不透明标识符”之类的东西)。解析器已经知道如何做到这一点。 - Marc Gravell
感谢您澄清这个问题,我非常想进入SQL服务器并查看跟踪调用,但由于政治原因我无法这样做。不管怎样,您非常有帮助,我会给您答案的! - Grofit
1
@Grofit 你不必这样做:LINQ-to-SQL有.Log属性,你可以附加任何文本输出(例如Console.Out或者一个StringWriter,如果我没记错的话)- 允许你监视它。另外,像mini-profiler这样的工具可以用来记录所有的ADO.NET流量(对于开发人员而言)。如果我(作为开发人员)在我的网站上加载一个页面,我可以看到所有发生的SQL操作。还有很多其他的东西。 - Marc Gravell
显示剩余2条评论

0
提供程序知道如何将自定义模型中填充的属性映射到数据库表上的实际列(因为查询中的选择子句),因此它知道在您过滤自定义模型的属性时需要过滤表上的哪个列。将所选模型(无论是具有表的所有列的设计师定义实体,还是在某个地方定义的自定义模型,或者仅包含所需数据的匿名类型)视为 SQL 查询中 FROM 子句之前的所选列。选择匿名模型使得很容易识别模型中的字段与 SQL 中的 SELECT 列相对应。
最重要的是:始终记住,var result = from ... 只是一个查询... 直到被迭代 result.ToArray()。尝试将变量命名为query而不是result,当您再次查看时,世界可能会呈现新的色彩。

0

谢谢提供的链接,我从未使用过它,更喜欢NHibernate或如果需要MS技术,则使用EntityFramework。我相信微软停止支持Linq2Sql,因为它只是在EF发布之前的一个填充物。因此,如果您仍在使用此框架,您可能需要寻找其中一种较新的框架。 - Grofit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接