【问题标题】:Linq To Sql Where does not call overridden EqualsLinq To Sql Where 不调用重写的 Equals
【发布时间】:2012-08-05 22:32:47
【问题描述】:

我目前正在做一个项目,我将对类似的非数据库(本讨论的服务层对象)对象和通过 LinqToSql 从数据库检索的对象进行大量比较。为了便于讨论,假设我有一个服务层 Product 对象,其中包含一个在数据库中表示的字符串字段。但是,在数据库中,还有一个主键Id,在服务层没有表示出来。

因此(就像我经常在单元测试等方面所做的那样),我覆盖了 Equals(Object)、Equals(Product) 和 GetHashCode 并实现了 IEquatable,并期望我能够编写如下代码:

myContext.Products.Where(p => p.Equals(passedInProduct).SingleOrDefault();

等等。

Equals 覆盖已经过测试并且可以工作。这些对象是可变的,因此通常的警告适用于 GetHashCode 覆盖。但是,就本示例而言,对象不会被 LtS 修改,并且可以设为只读。

这是一个简单的测试:

  • 在内存中创建一个测试对象并提交到 LtS 上下文。通过提交,测试对象会填充一些自动生成的字段。
  • 在内存中创建另一个相同的测试对象(单独引用)
  • 尝试使用第二个对象作为标准从数据库中检索第一个对象。 (见上面的代码行)。

        // Setup
        string productDesc = "12A";
        Product testProduct1 = _CreateTestProductInDatabase(productDesc);
        Product testProduct2 = _CreateTestProduct(productDesc);
    
        // check setup
        Product retrievedProduct1 = ProductRepo.Retrieve(testProduct1);
        //Assert.IsNotNull(retrievedProduct1);
    
        // execute - try to retrieve the 'equivalent' product object
        Product retrievedProduct2 = ProductRepo.Retrieve(testProduct2);
    

Retrieve 的简化版本(删除的部分只是参数检查等):

using (var dbContext = new ProductDataContext()) {
    Product retrievedProduct = dbContext.Products
         .Where(p => p.Equals(product)).SingleOrDefault();

注意:重写的 Equals 方法知道不关心数据库中自动生成的字段,只查看服务层中表示的字符串。

这是我观察到的: 在 testProduct1 上检索成功(毫不奇怪,通过引用相等) 检索 testProduct2 失败(空) 在 Retrieve 方法中调用的被覆盖的 Equals 方法在任何一个 Retrieve 调用期间都不会被命中 但是,SubmitChanges 上的上下文多次调用重写的 Equals 方法(在数据库中创建第一个测试对象时调用)(按预期工作)。

静态地,编译器知道被发射的对象的类型并且能够解析该类型。

所以我的具体问题:

  • 我是否试图做一些不明智的事情?似乎是对 Equals 的直接使用。
  • 第一个问题的推论:处理 linq to sql 相等性检查的替代建议,同时将比较详细信息保留在对象而不是存储库中
  • 为什么我发现 Equals 方法在 SubmitChanges 中被解析,但在 Where 子句中却没有?
  • 我对理解和使我的 Equals 调用正常工作一样感兴趣。但我也很想学习如何使这种“模式”发挥作用,而不仅仅是理解为什么它在 LtS 和 C# 的竞赛中看起来是一种“反模式”。

请不要建议我直接在上下文中使用 Where 语句进行过滤。显然,我可以删除 Equals 调用并执行此操作。但是,其他一些对象(此处未显示)很大且有点复杂。为了维护和清晰起见,我想了解如何在一个地方将自己与另一个自己的类型进行比较,最好是作为相关对象的一部分。

我尝试过的其他一些没有改变行为的事情:

  • 重载并使用 == 代替
  • 将 lambda 变量转换为类型 p => (Product)p
  • 首先获取 IQueryable 对象并在 Where 子句中调用 Equals

我尝试过的其他一些无效的方法:

  • 创建静态 ProductEquals(Product first, Product second) 方法:System.NotSupportedException:没有支持的 SQL 转换。

感谢 StackOverflow 贡献者!

关于可能的重复:我已经阅读了大约 10 个其他问题。我想要一个指向完全重复的指针,但大多数似乎并没有直接解决 LinqToSql 的奇怪之处。

【问题讨论】:

  • 顺便说一句 - 你没有覆盖 ==,你大概重载它,这不是一回事。运算符不会被多态地调用。
  • 啊,是的,超载了。感谢您的语言澄清。

标签: linq c#-4.0 linq-to-sql equals equals-operator


【解决方案1】:

我是否试图做一些不明智的事情?

当然。考虑一下 LINQ to SQL 的作用:它创建查询的 SQL 表示。它知道您覆盖的 Equals 方法的作用,因此无法将该逻辑转换为 SQL。

第一个问题的推论:处理 linq to sql 相等性检查的替代建议,同时将比较详细信息保留在对象而不是存储库中

您需要使用表达式树来表示相等性,然后将这些表达式树构建成一个完整的查询。这不会很有趣,但它应该是可能的。不过,它会影响您构建所有查询的方式。

我会预计大多数数据库表示是基于 ID 的,因此您应该能够仅比较 ID 是否相等。通常,当我看到尝试以 OO 方式对数据进行真正建模但将其存储在数据库中时,抽象的泄漏会造成很多痛苦。

为什么我可能观察到在 SubmitChanges 中解决了 Equals 方法,但在 Where 子句中没有解决?

大概SubmitChanges 正在处理一组内存中的对象以计算出发生了什么变化——它不需要对 SQL 进行任何转换来完成那部分。

【讨论】:

  • 我在超时之前想说的是:关于你关于表达式树的第二点:从上下文中获取查询,将其提供给对象,并用适当的装饰它是相对简单的Where 子句,并将其返回到上下文中。这确实满足了将代码与类型的对象与类型进行比较相关的目标。但它似乎不符合清晰的总体目标,因为我怀疑它会是未来维护代码的人不熟悉的模式。
猜你喜欢
  • 1970-01-01
  • 2017-01-23
  • 1970-01-01
  • 2011-04-12
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多