2015-04-07 6 views
20

Mój kolega dostał błąd z bardziej złożoną kwerendą przy użyciu LINQ do SQL w .NET 4.0, ale wydaje się być łatwo odtwarzalny w prostszych okolicznościach. Rozważmy tabelę o nazwie TransferJob z identyfikatorem syntetycznym i polem bitowym.Dziwne zachowanie w LINQ do SQL z anonimowymi obiektami i stałymi kolumnami

Jeśli wykonujemy następujące zapytanie

using (var ctx = DBDataContext.Create()) 
{ 
    var withOutConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = x.IsFromAutoRebalance }); 
    var withConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = true });//note we're putting a constant value in this one 

    var typeA = withOutConstant.GetType(); 
    var typeB = withConstant.GetType(); 
    bool same = typeA == typeB; //this is true! 

    var together = withOutConstant.Concat(withConstant); 
    var realized = together.ToList();//invalid cast exception 
} 

nieprawidłowy oddanych zostanie zgłoszony wyjątek, jeżeli nie zaznaczono. Dziwnie jednak mamy równość typu podczas przeglądania w debugerze.

prostu zmieniając przedostatniej kolejce, aby przejść z IQueryable do LINQ do obiektów

var together = withOutConstant.ToList().Concat(withConstant.ToList()); 
var realized = together.ToList();//no problem here 

wtedy wszystko działa dobrze, jak oczekiwano.

Po pewnym wstępnym wykopaniu, widzę, że wygląda na to, że programiści LINQ do SQL brali pod uwagę wydajność i faktycznie nie mają wygenerowanego SQL ciągnącego stałą wartość w przypadku z jawnym ustawieniem true w wersji withConstant.

Wreszcie, jeśli mogę przełączyć zamówić wszystko wydaje się działać:

var together = withConstant.Concat(withOutConstant); //no problem this way 

Jednak nadal bym chciał wiedzieć, czy lepiej szczegół, co naprawdę się dzieje. Uważam raczej za dziwne, że będą one traktowane jako równe typy, ale powodują wyjątek dotyczący nieprawidłowych rzutów. Co się właściwie dzieje pod kołdrą? Jak mógłbym udowodnić to sobie?

stosu Ślad:

at System.Data.SqlClient.SqlBuffer.get_Boolean() 
    at Read_<>f__AnonymousType2`2(ObjectMaterializer`1) 
    at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext() 
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) 
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) 
    at KBA.GenericTestRunner.Program.Main(String[] args) in c:\Users\nick\Source\Workspaces\KBA\Main\KBA\KBA.GenericTestRunner\Program.cs:line 59 
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) 
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart() 

Generated SQL jest następujący:

SELECT [t2].[TransferJobID] AS [Id], [t2].[IsFromAutoRebalance] AS [IsAuto] 
FROM (
    SELECT [t0].[TransferJobID], [t0].[IsFromAutoRebalance] 
    FROM [dbo].[TransferJob] AS [t0] 
    UNION ALL 
    SELECT [t1].[TransferJobID], @p0 AS [value] 
    FROM [dbo].[TransferJob] AS [t1] 
    ) AS [t2] 
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 

Z odwróconej kolejności (co nie psuje) SQL jest:

SELECT [t2].[TransferJobID] AS [Id], [t2].[value] AS [IsAuto] 
FROM (
    SELECT [t0].[TransferJobID], @p0 AS [value] 
    FROM [dbo].[TransferJob] AS [t0] 
    UNION ALL 
    SELECT [t1].[TransferJobID], [t1].[IsFromAutoRebalance] 
    FROM [dbo].[TransferJob] AS [t1] 
    ) AS [t2] 
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 

Aby mój wcześniejszy komentarz, stała nie jest ciągnięta podczas wykonywania

withConstant.ToList() 

SELECT [t0].[TransferJobID] AS [Id] 
FROM [dbo].[TransferJob] AS [t0] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 
+0

Czy możesz wysłać ślad stosu? To dałoby kilka wskazówek na temat tego, gdzie może leżeć problem. –

+0

Czy mógłbyś opublikować definicję klasy/model dla instancji 'T' w twoim' IQueryable 's? Ponadto, kiedy mówisz "syntetyczny identyfikator", masz na myśli automatyczny inkrement dla ciebie pk? – evanmcdonnal

+0

Dodano ślad stosu – nlh3

Odpowiedz

8

Podczas wyliczania w konstruktorze together.ToList(), staramy się przejść do następnego elementu w zapytaniu odroczone, że jest teraz rozwiązany.

MoveNext zamierza utworzyć obiekt z wyników bazy danych. Zapytanie bazy danych zostaje przekształcone w DataReader, a wiersz jest wyodrębniany z DataReader. Teraz get_Boolean jest zaimplementowany w taki sposób, że robi obiekt o wartości VerifyType i generuje wyjątek, jeśli jest niepoprawny.

Czego brakuje, aby pokazać w swoim pytaniu jest SqlText zapytania together „s (jak również _sqlText twoich ctx.TransferJobs), więc jestem zmuszony dokonać rozsądne założenie.

TRUE jest konwertowane na 1, a FALSE jest konwertowane na 0. Konwersja na bit promuje dowolną niezerową wartość do 1.

LINQ do SQL Source będzie trasform się Select dla parametru true w coś

([table].[column] = 1) 

i dla parametru false w

NOT ([table].[column] = 1) 

Tak - gdy pierwszy filtr nie jest w oparciu na warunku boolowskim true - powyższy wiersz kodu to miejsce, w którym może zostać użyty wyjątek odlewania, jeśli dostawca Linq otrzymuje obiekt, który nie jest równy 0 (lub co to jest false logiczna odpowiada), moje przypuszczenie jest zerowe.

- przypis -

pomocnik rejestrować rzeczywistą SQL pod zapytania Linq (oprócz tej Log własności Oczywiście)

Debug.WriteLine(together.ToString()); 

(lub GetQueryText(query) jak opisano w debugging support)

UPDATE

po obejrzeniu SQL, poprawka pracy jest po prostu mapować pole bitowe jak int jak poniżej, używając DbType obiekt

[global::System.Data.Linq.Mapping.ColumnAttribute 
(Storage="_IsFromAutoRebalance", DbType="INT NOT NULL")] 
      public bool IsFromAutoRebalance 
      { 
       get 
       { 
        return this._IsFromAutoRebalance; 
       } 

pokrewne (stary) VS zwrotnej link którym błąd został zamknięty jako Won't Fix z Sugerowane obejście:

5

To jest błąd L2S. Wynika to z następujących faktów:

  • To jest awaria wewnętrznego kodu L2S. Nie jest to kontrolowany/oczekiwany wyjątek.
  • To powinno działać.
  • Losowe zmiany w zapytaniu powodują zniknięcie awarii.

Modyfikuj zapytanie w sposób losowy, dopóki nie zadziała. Już masz dobre obejście. Pozostaw komentarz w języku C#, aby udokumentować, że to zapytanie zależy od obejścia problemu z błędem L2S.

Znalazłem prawdopodobnie kilkanaście błędów L2S na przestrzeni lat (podczas wydawania nietypowych lub skomplikowanych zapytań). Produkt został porzucony, więc w końcu wszyscy będziemy musieli przejść na EF. Czytam dzienniki zatwierdzenia EF i mają również błędy tłumaczenia zapytań.

Co się właściwie dzieje pod kołdrą?

Nie mogę odpowiedzieć na to bez większego dochodzenia. Możliwe jest debugowanie kodu źródłowego L2S, ale to dużo pracy. To pytanie jest ze względu na ciekawość tylko dlatego, że masz już obejście tego błędu.

Jak mogę udowodnić to sobie?

Dowód, że to błąd? Podałem kilka powodów powyżej.

Wygląda na to, że programiści LINQ do SQL brali pod uwagę wydajność i faktycznie nie mają wygenerowanego SQL ciągnącego wartości stałej w przypadku z jawnym ustawieniem true w wersji withConstant.

Nie wydaje mi się to możliwe. Jeśli to prawda, spodziewam się, że wszystkie przyciągane obiekty mają wartość true. Nie oczekiwałbym nieważnego rzutu, jeśli ta kolumna nie jest nawet wyciągnięta z bazy danych, jak sugerujesz. Myślę, że to jest błąd tłumaczenia zapytania.

Pomysł na innego obejścia byłoby:

IsAuto = x.IsFromAutoRebalance == x.IsFromAutoRebalance 

Teraz to już nie jest stała, ale zawsze będzie prawdą w czasie wykonywania. Optymalizator zapytań SQL Server może uprościć ten kod do 1. Mamy nadzieję, że L2S nie będzie już wykonywać zepsutej przeróbki.


Aktualizacja:

z kodu T-SQL, opublikowanego błąd jest oczywisty. Parametr @p0 to int, a nie bool. Powoduje to, że kolumna wynikowa będzie promowana do int according to the rules. Jest int w obu przypadkach. Podobno w jednym z przypadków L2S próbuje pobrać go jako bool, w drugim jako int. Pobieranie go jako bool nie działa i się zawiesza. Innym rozwiązaniem jest przekształcenie zapytania w ints (np. x.IsFromAutoRebalance ? 1 : 0 i 1).