|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Добрый день, Есть такая замечательная книжка, NHibernate CookBook, вроде как уже адаптированная под NH 3.0, у нас в проекте правда 2.0, но вряд ли это что-то изменит. Там был классный, замечательно продуманный пример для предка всех Entity, его я и заюзал недавно, а теперь вижу, что в переделанных под него сущностях возникли недетские, странные, неразрешимые проблемы :( Главным образом пришлось заюзать его из-за следующей цепочки: есть связи М:М -> не нужны лишние insert и delete при каждом обновлении коллекции -> пришлось использовать <set> и ISet (Iesi) -> нужно перегрузить для таких коллекций Equals -> кто ж как не авторы NHibernate знают, как это правильней сделать? -> использовать пример из их книжки NHibernate 3.0 CookBook (по крайней мере лидер команды NH и еще один член участвовали в разработке книги). Вот оно: Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78.
Типичный пример использования: Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38.
Беда в том, что иногда метод GetUnproxiedType() выдает все же тип прокси! Из-за этого метод Equals() выдал для двух entities с одинаковым id (когда после коммита unitofwork NH пытался писать в entity - в одно из полей то же самое значение, что и было), что они не равны. Полная печаль :( Как делают это профессионалы? Думал я, что тот пример Entity<> не умеет работать с наследованием, но он в той части книги, где как раз наследование есть. Реально у нас от Organization произошла COrganization, а от той - DOrganization. Вот создавалась связь этой DOrganization Мы можем сделать виртуальный метод Get...Type в базовой Entity, а потом его перегружать, но стоит ли? Реально у нас был сеттер одного из полей для защиты (раз уж сделали метод, меняющий поле, public в предыдущем примере от случайного изменения этого поля извне (по логике работы оно инициализировалось один раз при создании объекта и более не трогалось): Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
Вот конструировали новый Contract, который при конструировании ставил свое поле COrganization указывать на некую организацию. (И добавлял себя в коллекцию этой организации). Потом делали UnitOfWork.Commit, NH лезло в поле, писало в это поле организацию с тем же id, глючил GetUnproxiedType, потом неправильно они считались не равными, и сеттер ругался... Можно, конечно, не пользовать операцию != для сущности, раз она глючит, и обойтись лямбдой: (... && value != null && value.Id != cOrganization.Id). Но как-то все равно нехорошо :( Например, теоретически в коллекцию может добавиться двойной элемент (с одним Id, а NH подумает, что он не равен сам себе, как сейчас). Ошибка будет где-то на уровне SQL, я думаю. Мы конечно будем руками через лямбды каждый раз проверять, есть ли элемент с таким Id в коллекции, но все равно как-то грустно. Сейчас надо весь проект переделать под новое Entity<>, а переделывать его на неработающий код как-то не лежит душа :( ... |
|||
:
Нравится:
Не нравится:
|
|||
03.06.2011, 11:28 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Вот этот поганец в методе Entity.Equals(Entity<TId> other) : Код: plaintext 1. 2. 3. 4. 5.
собственно otherType == COrganizationProxy thisType == DOrganization От COrganization произошли COrganizationProxy и DOrganization. Естественно, что они не являются IsAssignableFrom друг друга... Может, не стОило делать перегрузку операций != == Equals GetHashCode у потомков базового Entity? В книге вроде только для базового определено оно... Но у нас уж для пары классов она была перегружена, так чтобы не убирать == и != по всему проекту для данных entity, пришлось в них переделать == и != с учетом нового Equals. ... |
|||
:
Нравится:
Не нравится:
|
|||
03.06.2011, 12:29 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Так заменить obj == obj на obj.id == obj.id это полдела. У нас широко используются Contains, Intersect. Тема уже обсуждалась, даже говорили, что все работает. Но мне совершенно непонятно, как может с прокси корректно работать такой пример оттуда: Код: plaintext 1. 2.
... |
|||
:
Нравится:
Не нравится:
|
|||
03.06.2011, 15:00 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Видимо, все, что могу предложить - раз все уперлось в неправильное определение типа под прокси - можно 100% решить данную проблему путем введения в Entity виртуального метода Type GetUnproxiedType() и переопределять его во всех потомках, а в самом Entity его и определять не нужно. (Или пусть возвращает null, т.о. если забудем метод переопределить в потомке, он проверит и кинет исключение). Еще подводный камень, что в более далеких коленах наследования можно забыть переопределить его, и они будут неправильно возвращать тип предка. Выглядит костылем, но должно работать Код: plaintext 1. 2. 3. 4. 5. 6. 7.
Может, можно как-то попытаться извратиться через genericи. Только не уверен, сработает ли Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Странно, что такая книга от самих авторов NHibernate ввела в такой тупик. Может быть, они не тестировали свой код на сложных иерархиях классов? Осталось ждать только веского слова Solyutorа... Уж он скажет, так скажет. Хотелось бы знать, что он думает по поводу примера entity из данной книги. public virtual bool Equals(Entity<TId> other) - что-то меня терзают подозренья, что авторы не зря сделали этот метод public, хотя могли бы private. Хотят, чтобы мы переопределяли его в каждом потомке, что ли? Может, в этом все дело? ... |
|||
:
Нравится:
Не нравится:
|
|||
07.06.2011, 19:45 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Давайте по порядку. Начнём с прокси. Пример из книжки мне представляется вводящей в заблуждение ошибкой: Код: plaintext 1. 2. 3.
Если уж очень хочется узнать настоящий тип, можно использовать статический класс NHibernateProxyHelper. Там уже есть нужные методы расширения. По поводу эквивалентности - я использую этот базовый класс . Но он тоже выдаст false при сравнении прокси/непрокси объектов. Его можно модифицировать для сравнения и прокси объектов, но тогда в вашей модели будут явные ссылки на NHibernate (см. класс выше.) ... |
|||
:
Нравится:
Не нравится:
|
|||
08.06.2011, 10:30 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Вот то-то и оно, только без связывания (полного) entity с NHibernate можно обойтись, на мой взгляд, единственным способом (описанным мной) - ввести виртуальный GetUnproxiedType. Вообще тут и так косвенно все связалось с NHibernate (те же Iesi Collections), но как-то не на 100%, т.е. нет зависимостей от кода NHibernate (Iesi не является его частью). Предположим, они хотят, чтобы Entity ни от чего стороннего не зависел; вроде бы тут только один выход - ввести этот виртуальный метод? ... |
|||
:
Нравится:
Не нравится:
|
|||
08.06.2011, 18:46 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
В примере по Вашей ссылке тоже вижу на первый взгляд ошибку, что это? !GetType().IsInstanceOfType(other)) - а как же симметричность, т.е. во вторую сторону не проверяют? Является ли A экземпляром B - мало, может быть, что нет, зато B является экземпляром A. Вообще обычно сравнивают объекты одного типа, ну или A с прокси A. Потому и не огребают проблем. (Хотя предыдущий абзац намекает, что и в этом случае огребут). А у топикстартера есть иерархия. Второй вопрос - про Contains() и Intersect(). В нем я не специалист. Тут на форуме кто-то уже спотыкался, вот тут у Майкрософт написано много всего, но как-то неудобно выходит, надо наследоваться от EqualityComparer<T> и потом явно передавать этот comparer внутрь словаря ... фууу... We recommend that you derive from the EqualityComparer<T> class instead of implementing the IEqualityComparer<T> interface, because the EqualityComparer<T> class tests for equality using the IEquatable<T>.Equals method instead of the Object.Equals method. This is consistent with the Contains, IndexOf, LastIndexOf, and Remove methods of the Dictionary<TKey, TValue> class and other generic collections. ... |
|||
:
Нравится:
Не нравится:
|
|||
08.06.2011, 19:45 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
OVoronovВ примере по Вашей ссылке тоже вижу на первый взгляд ошибку, что это? !GetType().IsInstanceOfType(other)) - а как же симметричность, т.е. во вторую сторону не проверяют? Является ли A экземпляром B - мало, может быть, что нет, зато B является экземпляром A.Вообще обычно сравнивают объекты одного типа, ну или A с прокси A. Потому и не огребают проблем. (Хотя предыдущий абзац намекает, что и в этом случае огребут). А у топикстартера есть иерархия. Как водится, я привёл один из вариантов. !GetType().IsInstanceOfType(other)) лишь проверяет, что объекты однотипны, и не проверяет наследование. Вам решать эквивалентен ли наследник предку. Нет так сложно заменить на IsAssignableFrom, да и разрулить ситуацию с прокси - пара строчек. OVoronovВторой вопрос - про Contains() и Intersect(). В нем я не специалист. Тут на форуме кто-то уже спотыкался, вот тут у Майкрософт написано много всего, но как-то неудобно выходит, надо наследоваться от EqualityComparer<T> и потом явно передавать этот comparer внутрь словаря ... фууу... Здесь соглашусь, использовать EqualityComparer<T> тут и там не очень красиво, хотя иногда это и есть решение. ... |
|||
:
Нравится:
Не нравится:
|
|||
09.06.2011, 08:42 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Лучше всего, я думаю, гарантировать проверку по Contains и Intersect через наши критерии, т.е. что предок эквивалентен потомку, и т.п. Т.о., приходим к тому, что как-то надо вызывать наше Equals из Contains. Как это сделать проще всего? А что предок эквивалентен потомку, как насчет такого: иерархия A->B, A->C, в объекте D есть коллекция предков - объектов A (для общности). Хотя реально туда добавляются объекты либо B, либо С, т.е. потомки. Естественно, что тогда A надо считать равным B. (При одинаковом id). Если кому не нравится пример со смешиванием B и C в одной коллекции, то можно его видоизменить: пусть от объекта D происходят объекты Db и Dc, у объекта D есть коллекция элементов A, но реально элементы добавляются не через объект D, а через его реализации Db и Dc: Db имеет всегда коллекцию элементов типа B, а Dc имеет коллекцию C. Логично было вынести коллекцию в общий предок объектов Db и Dc, соответственно это не коллекция B или С, а коллекция их общих предков же. ... |
|||
:
Нравится:
Не нравится:
|
|||
09.06.2011, 14:47 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Добрый день! Так в чем сошлись - подходит ли нам лучше всего виртуальный метод GetUnproxType, или как? Требования у нас такие: а) Никакой зависимости у сборки с Entities от NHibernate б) Прокси требуется учитывать, и иерархию тоже. Т.е. все эти объекты должны иметь одинаковое значение при одинаковом ненулевом id: A, proxy A, B:A, proxy B, C:B, proxy C. в) Коллекции должны быть, proxy и lazyload использоваться и работать, и связи М:М тоже. На данный момент невыясненными остаются вопросы: - Как лучше реализовать Equals(), чтобы оно удовлетворяло всем вышеуказанным требованиям - Как гарантировать работу Contains() и Ко. с учетом их же. ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 10:55 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Вот здесь чувак в 3-ем посте (т.е. во 2-м ответе) почему-то тоже утверждает, что GetType() будет работать как unproxy, и что он это использует. С чего они все так считают? Код: plaintext 1. 2. 3.
Я сейчас перечитал материал и засомневался даже в коде, который делает загрузку сущностей предка (которые на самом деле являются экземплярами потомков). Вот наш пример. Тут все правильно? (от A происходит потомок B) Код: plaintext 1. 2. 3. 4.
С другой стороны, некий Михельсон ( Diego Mijelshon ) пишет, что метод, возвращающий this, вообще является проблемой по трем пунктам: 1. Это "задняя дверь" к объекту, лежащему внутри. На самом деле им должен управлять NH. 2. Можно использовать это только для доступа к свойствам унаследованного класса. Для поведения надо юзать полиморфизм. 3. Никогда не надо передавать этот (полученный по this) объект в методы типа Update или Delete. Кстати, вопрос, если вместо GetUnproxiedType() мы сделаем для каждой сущности (а не только для полиморфных, как выше) метод GetConcrete(), возвращающий this, и будем дергать его из Equals() - не приведет ли его вызов к полной загрузке всех коллекций (даже если они с lazyload)? А если сделаем метод GetUnproxiedType(), возвращающий конкретный Type, не будет ли он возвращать A вместо B? Т.е. вызовется его реализация для предка A, т.к. NHibernate вернет при загрузке поля типа A (см. выше) именно прокси типа A? ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 12:29 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
NHibernate_User_Добрый день! Так в чем сошлись - подходит ли нам лучше всего виртуальный метод GetUnproxType, или как? Требования у нас такие: а) Никакой зависимости у сборки с Entities от NHibernate б) Прокси требуется учитывать, и иерархию тоже. Т.е. все эти объекты должны иметь одинаковое значение при одинаковом ненулевом id: A, proxy A, B:A, proxy B, C:B, proxy C. в) Коллекции должны быть, proxy и lazyload использоваться и работать, и связи М:М тоже. На данный момент невыясненными остаются вопросы: - Как лучше реализовать Equals(), чтобы оно удовлетворяло всем вышеуказанным требованиям - Как гарантировать работу Contains() и Ко. с учетом их же. Попробуйте такой класс, сделанный на основе приведённого мною выше. На абсолютную правильность не тестировал, но надеюсь идея будет понятна. Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51.
... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 12:42 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Спасибо, попробуем. Но на вид other.GetType() невиртуально? Самое смешное, что автор NH утверждает, что данный пример будет работать, и вот почему: "If you don't understand how the proxy frameworks work, the idea can seem magical. When NHibernate returns a proxy for the purposes of lazy loading, it returns a proxy instance inherited from the actual type. There are a few members we can access without forcing a load from the database. Among these are proxy's Id property or field, GetType(), and in some circumstances Equals() and GetHashCode(). Accessing any other member will force a load from the database. When that happens, the proxy creates an internal instance. So, for example, a lazy loaded instance of Customer (CustomerProxy102987098721340978), when loaded, will internally create a new Customer instance with all of the data from the database. The proxy then does something like this: Код: plaintext 1.
So, all calls to the Name property on the proxy are relayed to the internal Customer instance that has the actual data. GetUnproxiedType() takes advantage of this. A simple call to GetType() on the proxy will return typeof(CustomerProxy02139487509812340); A call to GetUnproxiedType() will be relayed to the internal customer instance, and the internal customer instance will return typeof(Customer)." Только вот почему у нас не работает? NH 2.0 или 2.1 не так работает, как 3.0? Или дело в том, что мы переопределили Equals в самом классе (а не предке Entity), и вызываем base.Equals()? Или потому, что что-то случилось при вызове Код: plaintext
Вдогонку вопрос, что будет с не загруженными еще коллекциями объекта, если мы для его прокси вызовем GetConcrete(), который вернет this? Или они будут пустыми, или как? А обращение к этим коллекциям у возвращенного GetConcrete() объекта (не у прокси!) не приведет к их загрузке через LazyLoad? Как же тогда быть? ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 17:25 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Фффух, по-моему, я победил этот баг, когда заменил "private Type GetUnproxiedType()" из книжки на "public virtual Type GetUnproxiedType()", все заработало (NH 2.0 or 2.1 у нас). Почему же в книжке написано "private"? Кто-то пытался пост-редактировать, скрыв ненужный вроде бы снаружи метод, и получил скрытый баг? :) А впрочем тут и написано, что работает сей метод только под Castle Proxy, а под другими (у нас LinFu), включая свой прокси из NH 3.2, оно не работает :) лол ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 18:38 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Вторую проблему победил (с GetConcrete()). LinFu - настолько кривой, что вылетали разные страшные эксепшены при попытке реализовать через дженерики. (type argument 'T' violates the constraint of type parameter, Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true) Кончил тем, что Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Как вам такое решение? У меня под LinFu благополучно распроксирует, и Lazy коллекции не портятся, а в БД идут запросы на них, когда в отладчике просматриваю и к ним обращаюсь. ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 20:43 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Третий и последный затык, только он вроде и остался нерешенным - это как удобно и элегантно перегрузить default comparer для dictionary, чтоб без боязни вызывать Contains(), Intersects(). (И для Iesi.Iset, видимо, тоже можно вызвать Contains). Помнится, NHibernate что-то там гарантировал, какую-то там гарантированную эквивалентность двух объектов в памяти, если они соответствуют одной и той же записи в БД, или это не так? Может, он и для Contains это гарантирует, нет? ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 21:16 |
|
Общий предок для всех Entity (NHibernate) - не работает получение типа под прокси
|
|||
---|---|---|---|
#18+
Сейчас проверил - оч. странно - прочитал прокси из базы, а также в памяти создал совершенно новый объект с разными полями, но с тем же Id - так и чтение по ключу из словаря, и Contains для созданного List() работают правильно, оставляю все то же, меняю только id в отладчике - все опять верно, entity начинают не содержаться ни в словаре, ни в списке. Но почему отладчик не входит по дебагу в Equals??? И почему он все же учитывает именно его??? ... |
|||
:
Нравится:
Не нравится:
|
|||
10.06.2011, 21:39 |
|
|
start [/forum/topic.php?fid=17&msg=37301755&tid=1350743]: |
0ms |
get settings: |
8ms |
get forum list: |
10ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
146ms |
get topic data: |
8ms |
get forum data: |
2ms |
get page messages: |
47ms |
get tp. blocked users: |
1ms |
others: | 12ms |
total: | 240ms |
0 / 0 |