Linq To Sql, где не называется переопределено равно

В настоящее время я работаю над проектом, в котором я собираюсь провести много сравнений похожих не связанных с базой данных (объектов сервисного уровня для этого обсуждения) объектов и объектов, извлеченных из базы данных через LinqToSql. Ради этого обсуждения предположим, что у меня есть объект Product уровня обслуживания со строковым полем, которое представлено в базе данных. Однако в базе данных также есть идентификатор первичного ключа, который не представлен на уровне обслуживания.

Соответственно (как я часто это делаю для модульного тестирования и т. Д.), Я переопределил 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();

NB. Переопределенный метод Equals не заботится об автоматически сгенерированных полях из базы данных и рассматривает только строку, представленную на сервисном уровне.

Вот что я наблюдал: получение по testProduct1 завершилось успешно (не удивительно, равно по ссылке). Извлечение по testProduct2 завершилось неудачно (null) Переопределенный метод Equals, вызываемый в методе Retrieve, никогда не срабатывает во время обоих вызовов Retrieve. Однако переопределенный метод Equals вызывается несколько раз контекстом на SubmitChanges (вызывается при создании первого тестового объекта в базе данных) (работает как положено).

Статически, компилятор знает, что тип испускаемых объектов и может разрешить тип.

Итак, мои конкретные вопросы:

  • Я пытаюсь сделать что-то дурное? Похоже на прямое использование Равных.
  • Следствие к первому вопросу: альтернативные предложения по проверке равенства linq to sql, сохраняя при этом детали сравнения внутри объектов, а не в хранилище
  • Почему я мог наблюдать, как метод Equals разрешается в SubmitChanges, а не в предложении Where?
  • Я так же заинтересован в понимании, как и в том, чтобы мои звонки на равных работали Но я также хотел бы узнать, как заставить этот "шаблон" работать, а не просто понять, почему он выглядит "анти-шаблоном" в конкурсе LtS и C#.

Пожалуйста, не предлагайте мне просто фильтровать непосредственно по контексту с помощью утверждений Where. Очевидно, я могу удалить вызов Equals и сделать это. Однако некоторые другие объекты (не представленные здесь) являются большими и немного сложными. В целях поддержания и ясности я хочу сохранить знания о том, как сравнивать себя с другим своего типа в одном месте и в идеале как часть рассматриваемого объекта.

Некоторые другие вещи, которые я пробовал, не изменили поведение:

  • Перегружен и используется == вместо
  • Приведение лямбда-переменной к типу p => (Product)p
  • Сначала получить объект IQueryable и вызвать Equals в предложении Where

Некоторые другие вещи, которые я пробовал, не работали:

  • Создание статического метода ProductEquals("Сначала продукт", "Второй продукт"): System.NotSupportedException: не поддерживается перевод в SQL.

Спасибо авторам Stackru!

Re возможных дупс: я прочитал ~10 других вопросов. Я бы хотел указатель на точный дубликат, но большинство, похоже, не имеют прямого отношения к тому, что кажется странным в LinqToSql.

1 ответ

Решение

Я пытаюсь сделать что-то дурное?

Абсолютно. Рассмотрим, что делает LINQ to SQL: он создает представление SQL вашего запроса. Он не знает, что ты переопределил Equals метод, поэтому не может перевести эту логику в SQL.

Следствие к первому вопросу: альтернативные предложения по проверке равенства linq to sql, сохраняя при этом детали сравнения внутри объектов, а не в хранилище

Вам нужно будет что-то сделать с деревьями выражений, чтобы таким образом представить равенство, а затем встроить эти деревья выражений в полный запрос. Это не будет весело, но это должно быть возможно. Это повлияет на то, как вы строите все свои запросы.

Я ожидал бы, что большинство представлений базы данных будут основаны на идентификаторах, так что вы можете просто сравнить идентификаторы на равенство. Обычно, когда я видел попытки по-настоящему смоделировать данные, но сохранить их в базе данных, утечка абстракции вызвала большую боль.

Почему я мог наблюдать, как метод Equals разрешается в SubmitChanges, а не в предложении Where?

предположительно SubmitChanges работает с набором объектов в памяти, чтобы выяснить, что изменилось - ему не нужно выполнять какое-либо преобразование в SQL, чтобы выполнить эту часть.

Другие вопросы по тегам