Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

dev2dev

Ostrożnie z tą siekierką Eugeniuszu!

Wracam do blogowania po dłuższej przerwie (ech, minęło dokładnie dwa lata!).

Jako rasowy programista T-SQL nie byłem entuzjastą Linq2SQL ale postanowiłem się z nim zapoznać bliżej.
Miałem dość archaicznie zbudowaną bazę (nie była mego autorstwa!) i spróbowałem zrobić dość proste zapytanie oparte na złączeniu dwóch tabel.

Przedstawię problem w uproszczeniu nie podając oryginalnych tabel bo by się wydało o jaki system chodzi :)

Tabele są dwie i oto ich definicje:

CREATE TABLE [dbo].[T1](
    [Id] [int] NULL,
    [Col] [varchar](50) NULL,
    [Key] [int] IDENTITY(1,1) NOT NULL,
 CONSTRAINT [PK_T1] PRIMARY KEY CLUSTERED
(
    [Key] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF



Oraz druga tabela:

CREATE TABLE [dbo].[T2](
    [Id] [int] NULL,
    [Col] [nchar](10) NULL,
    [Key] [int] IDENTITY(1,1) NOT NULL,
 CONSTRAINT [PK_T2] PRIMARY KEY CLUSTERED
(
    [Key] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

W aplikacji utworzyłem model w Entity Framework:




Tabele miały następujące dane:

Tabela T1:



Tabela T2:



W LINQ zrobiłem zapytanie łączące te tabel poprzez INNER JOIN.
Złączenie będzie na kolumny null-owalne Id w obu tabelach.

oto fragment programu w C#, który to realizował:



Przy tych danych spodziewałem się dwóch rekordów w wyniku dla id równego 1 i 2.

Uruchomiłem program i otrzymałem wynik:



A co robi tutaj ten rekord podkreślony przez mnie czerwoną ramką? odpowiedź może przynieść jedynie SQL Server Profiler.
Podsłuchując to zapytanie otrzymujemy odpowiedź:



W czerwonej ramce mamy odpowiedź dlaczego otrzymaliśmy taki wynik

OR (([Extent1].[Id] IS NULL) AND ([Extent2].[Id] IS NULL))

Ten fragment zapytania musi budzić trwogę u programisty bazy danych. LINQ po prostu traktuje NULL jako wartość! W Lin2Objects może sobie tak robić ale w Linq2SQL nie ma prawa tak postępować. Edgar Frank Codd przewraca się w grobie.

A wracając do tytułu notki, to fani zespołu Pink Floyd wiedzą o co chodzi. Tym, którzy nie znają polecam ostatnie fragmenty tego utworu.

Opublikowane 17 listopada 2012 21:34 przez marekpow
Filed under:

Komentarze:

 

Abc said:

i raczej dobrze - mniejsze zaskocznie dla "usera".

inaczej ponizsze dawaly by inne rezultaty:

from t1 in dc.T1

join t2 in dc.T2 on ...

(wykonywane jako calosc przez L2S

from t1 in dc.T1.ToArray()

join t2 in dc.T2.ToArray() on ...

(wykonywane jako 2xAllZBazy[materializacja] i łączenie "w pamięci")

Ale rozumiem ze co intuicyjne dla "bazodanowca" moze byc zaskocznien dla "kodowca" i odwrotnie.

listopada 17, 2012 22:48
 

marekpow said:

Nie jest dobrze. Wynik jest sprzeczny z teorią i praktyką baz danych. NULL w baie danych nie jest wartością. Ozancza stan nieokreśloności. Wystarczy prosty test w T-SQL:

declare @a int = null

declare @b int = null

if (@a = @b) print 'Dziwne...'

else print 'Nie mają prawa być sobie równe'

nulll progamistyczny to nie to samo co NULL bazodanowy. Więc L2S i L2O powinny zwrcać różne wyniki.

Al bo coś sie nazywa LINQ 2 SQL albo nie. Bo trzymany rezultat jest nieoczekiwany.

listopada 18, 2012 20:33
 

Abc said:

"nie jest dobrze" nie wynika nijak z "teorii i praktyki db".

"kod" SQL napisany WYGLADA jako kod "kodowy".

Ja bym nie zakladal tak wprost ze:

"declare @a int" oznacza cos jak "int a"/"int? a"

czy

"@a = @b" to cos podobnego do "rownasie" z "a == b".

zauwaz ze L2S jest pomostem pomiedzy 2 wyspami (Sql i Object).

Stojac na wyspie Sql widzisz ze jest zle (jest nie spojne z reszta wyspy Sql; jest zaskakujace).

Stojac na wyspie Object i patrzac to wyglada spojnie (null to null to wartosc, wiec dzialajak wartosc).

Wiec punkt widzenia/wynikania jest pochodna aktualnego punktu "siedzenia".

:-)

Wydaje mi sie ze problem wynika z PODOBIENSTWA tekstu, ktore to podobienstwo translujesz na interpretacje/znaczenie kodu:

"SELECT... FROM ... JOIN ..." =~= "from .. in ... join ...".

Zauwaz ze zapisales swoje zyczenie do dżina w c#, wiec uzyles semantyki tego jezyka, chociaz wygladala jak ta SQLowa i sie kompilowalo wiec miales racje oczekiwac ze zadziala jak semantyka SQL bo z tego Twoje doswiadczenia sie wywodza :-)

Zawsze (poza trywialnymi przejsciami) przejscie pomiedzy domenami/jezykami jest stratne:

"mysli => mowa => interpretacja", "jezyk A => jezyk B".

Zreszta - czy to wazne? Kwiatki mozna znalesc i w innych mejscach i nawet w druga strone przechodzac od c# do SQL :-)

listopada 18, 2012 22:33
 

marekpow said:

Ale JOIN w LINQ odpowiada INNER JOIN w SQL. Nie ma prawa do złączenia takich rekordów, gdzie kolumna złączenia zawiera NULL!

W teorii RDB NULL jest symbolem nieokreśloności, nie jest wartością. Stąd moje powoływanie sie na na ducha Edgar Frank Codd.

listopada 19, 2012 09:55
 

Abc said:

A powolasz sie gdzies na to ze Linq "join" semantycznie ma dzialac jak SQL "INNER JOIN"?

Jak juz pisalem - osoba pochodzaca ze swiata "SQL" widzac zapis linq ma inne oczekiwania co do dzialania niz osoba pochodzaca spoza SQL.

Z punktu widzenia ktory jest dla MNIE najblizszy (tak z 70%), to traktowanie nulla jako wartosci ma najwiekszy sens - ale dopuszczam mozliwosc istnienia innej ontologii :-)

listopada 19, 2012 10:09
 

marekpow said:

INNER JOIN jest w tym co podpowiada SQL Server Profiler po skompilowaniu tego zapytania. Więc jest to semantyczny odpowiedniki. Jeżeli weźmiesz jakikolwiek podręcznik LINQ 2 SQL to bedziesz miał czarno na białym, że jest to odpowiednik INNER JOIN. Aby wygeneroć odpowiednik LEFT OUTER JOIN to trzeba to zupełnie inaczej napisać w LINQ.

A swoją drogą, jeżeli null ma być wartością to spróbuj ją zrzutować na inny typ w .NET.

listopada 19, 2012 11:03
 

sulavix said:

ammagamma Marek; dobry tekst;

lutego 11, 2013 18:18
 

Marek Adamczuk said:

Marku,  

Przecież możliwość złączenia nulli po SQL-owemu nadal jest. Wystarczy przestawić ansi_nulls na off i inner join "zadziała". Poza tym czasami takie złączenie, jak pokazałeś, jest często jak najbardziej pożądane - wielokrotnie sam takie pisałem. Co ciekawe, jakbyś położył indeksy na polach id, to przy takim zapytaniu sql server normalnie z nich skorzysta (tzn. potraktuje null jak wartość) i wcale nie będzie mu ten OR przeszkadzał. Oczywiście, to co napisałem, nie zmienia bynajmniej mojego podejścia do LINQ i wszystkich jemu wynalazków.

marca 19, 2013 21:30
 

marekpow said:

Marku,

to fakt, ale Ty to robisz świadomie. Natomiast tutaj progrmaista kompletnie nie wie, że takie coś mu zafunduje EF.

marca 20, 2013 09:52
Komentarze anonimowe wyłączone
W oparciu o Community Server (Personal Edition), Telligent Systems