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, чтобы выполнить эту часть.