Spis treści:

Kategoria:NHibernateOracle


BLOB o rozmiarze 0 i NHibernate na bazie Oracle

Czy wartość w polu BLOB może mieć rozmiar 0 bajtów? Co taka wartość może oznaczać? Czy nie lepiej wstawić tam po prostu NULL? Skoro rozmiar jest równy zero, to znaczy, że nic tam nie ma. Odpowiedzi na to pytanie nie są jednoznaczne. W większości przypadków NULL i pusty BLOB to będzie to samo, ale nie zawsze. Ci, którzy pamiętają jeszcze czasy C wiedzą, że pusty łańcuch znaków i pusty wskaźnik z punktu widzenia kodu to nie to samo. Podobnie jak zero, chociaż to jest tyle co nic, to nie jest to to samo co NULL, czyli brak wartości. Znacznie bardziej zaskakujące efekty możemy zaobserwować korzystając z typu BLOB w powiązaniu z NHibernate. Przejdźmy zatem do konkretów.

Pobieranie BLOB przy pomocy NHibernate

Aby pobrać obiekt BLOB przy pomocy NHibernate należy go odpowiednio zamapować. Można te dane powiązać z właściwością, ale można do tego użyć pola, pozostawiając właściwości zadanie konwersji tych danych binarnych na coś bardziej .NET-owego, powiedzmy obrazek lub dokument. Przykładowe mapowanie takiego pola w klasie może wyglądać następująco:

<property name="Photocolumn="OS_PHOTOtype="BinaryBlob"  />

Pozostałą część mapowania pominięto. Przypuśćmy, że tabela ma nazwę Tabela, kolumna z obiektem BLOB ma nazwę Photo, a nasz rozpatrywany rekord ma identyfikator 123. Tworzymy sobie proste zapytanie HQL następującej treści:

SELECT T.Photo FROM Tabela T WHERE T.Id=123

Wykonujemy zapytanie i obserwujemy efekty - pojawia się taki oto wyjątek:

System.ArgumentOutOfRangeException: Invalid destination buffer (size of 0) offset: 0
Parameter name: bufferoffset
at System.Data.OracleClient.OracleColumn.GetBytes(NativeBuffer_RowBuffer buffer, Int64 fieldOffset, Byte[] destinationBuffer, Int32 destinationOffset, Int32 length)
at NHibernate.Type.AbstractBinaryType.Get(IDataReader rs, Int32 index)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Persister.Entity.AbstractEntityPersister.Hydrate(IDataReader rs, Object id, Object obj, ILoadable rootLoadable, String[][] suffixedPropertyColumns, Boolean allProperties, ISessionImplementor session)
at NHibernate.Loader.Loader.LoadFromResultSet(IDataReader rs, Int32 i, Object obj, String instanceClass, EntityKey key, String rowIdAlias, LockMode lockMode, ILoadable rootPersister, ISessionImplementor session)
at NHibernate.Loader.Loader.InstanceNotYetLoaded(IDataReader dr, Int32 i, ILoadable persister, EntityKey key, LockMode lockMode, String rowIdAlias, EntityKey optionalObjectKey, Object optionalObject, IList hydratedObjects, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRow(IDataReader rs, ILoadable[] persisters, EntityKey[] keys, Object optionalObject, EntityKey optionalObjectKey, LockMode[] lockModes, IList hydratedObjects, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister)
--- End of inner exception stack trace ---

Oraz podobny, bezpośrednio związany z tym wyjątkiem komunikat:

ADOExceptionReporter NHibernate.Util.ADOExceptionReporter : Invalid destination buffer (size of 0) offset: 0
Parameter name: bufferoffset

Co ciekawe, jeżeli obiekt BLOB jest ustawiony na NULL, wszystko działa poprawnie. Jeżeli coś w nim jest, w sensie takim, że jego rozmiar jest większy niż 0, wynik też jest zwracany poprawnie. Problem pojawia się dla obiektu BLOB mającego wartość, ale bez żadnych danych. Coś na kształt pustego łańcucha znaków o rozmiarze 0 bajtów. Przy okazji: jak sprawdzić rozmiar danej w kolumnie typu BLOB? Tak jak poniżej:

SELECT dbms_lob.getlength(Photo) FROM Tabela WHERE Id=123

Rozwiązanie problemu

Pierwszy etap rozwiązywania problemu to poszukiwania w google. Niestety poszukiwania zakończyły się niepowodzniem. Drugi etap to pobudzenie neuronów i uruchomienie procesów myślowych. Nie wiem jakie są zalecenia twórców biorących udział w tworzeniu wymienionych w artykule technologii, tj. klienta Oracle oraz NHibernate, więc wymyśliłem rozwiązanie własne, które doprowadziło właśnie do opisanych wniosków - BLOB o rozmiarze 0 jest z natury ZŁY, NULL jest dobry. Uznałem, że obiekty BLOB o rozmiarze 0 są niepotrzebne, więc we wszystkich takich miejscach powstawiałem dobry NULL. Stał się cud i nagle wszystko zaczęło działać. Skrypt wyglądał następująco:

UPDATE Tabela SET Photo=NULL WHERE dbms_lob.getlength(Photo)=0

Przy okazji należałoby się zapytać, czy nie ma innych takich miejsc w systemie? Może w innych tabelach też są takie pola? Pomóc może następujący skrypt:

SELECT TABLE_NAME || '.' || COLUMN_NAME FROM user_tab_columns WHERE DATA_TYPE='BLOB'

Mam nadzieję, że każde następne poszukiwania w google zakończą się już pomyślnie, dzięki temu skromnemu wpisowi. Nie mam gwarancji, że jest to rozwiązanie gwarantujące sukces - w moim przypadku pomogło, więc liczę, że pomoże także innym. Ciekaw jestem, jakie są wasze opinie na ten temat.

Kategoria:NHibernateOracle

, 2013-12-20

Komentarze:

ultraSsak (2015-07-21 11:39:25)
Dziekanat został uratowany!
Dzięki Pawle ;)
Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za rozjasnienie zagadnienia upsert. wlasnie sie ucze programowania :).
Co się stanie gdy spróbuję wyszukać:
SELECT * FROM NV_Airport WHERE Code='SVO'
SELECT * FROM V_Airport WHERE Code=N'SVO'
(odwrotnie są te N-ki)
Będzie konwersja czy nie znajdzie żadnego rekordu?