Daily Archives: 2007.05.22. 23:09

DLINQ: Egyszerű tárolt eljárások esete az MSDN-nel

Egyszerűnek tekinthetjük az alábbi, elég tipikusnak mondható tárolt eljárást, ami visszaadja egy adott tábla rekordjainak számát:

    CREATE PROCEDURE dbo.GetCustomerCount
    AS
        SELECT Count(*) FROM Customers

Amikor megpróbáljuk meghívni, ugyanúgy járunk el, mint máskor, rádobjuk a tárolt eljárást a Visual Studio O/R Designerére, ami generál nekünk egy éppen megfelelő metódust. Szinte eláll a szívünk, amikor meglátjuk az így előállt C# metódust és már hívjuk is lelkesen, hiszen int a visszatérési érték:

    NorthwindDataContext dc = new NorthwindDataContext();
    int i = dc.GetCustomerCount();
    Console.WriteLine( i );

Bár a tárolt eljárás szinte művészien tökéletes, szomorúan konstatáljuk, hogy bizony mindig nullát ad vissza. Nézzük meg a generált metódus belsejét:

    [StoredProcedure( Name = "dbo.GetCustomerCount" )]
    public int GetCustomerCount()
    {
        IExecuteResults result = this.ExecuteMethodCall(
            this,
            (MethodInfo) MethodInfo.GetCurrentMethod() );
        return ( (int) ( result.ReturnValue ) );
    }

A kérdés – amire az MSDN-ből aligha kapunk választ a mostani béta változatban -, hogy mi az az IExecuteResults és mit tud a ReturnValue tulajdonsága? Némi Reflectorozással odáig sikerült eljutnom, hogy a DataContext.ExecuteMethodCall meghívja az aktuális provider ExecuteNonQuery metódusát, de mivel az is interfész alapú (IProvider), így az implementációval először nem sokáig jutottam. (Ezen a ponton arra tippeltem, hogy ez az ExecuteNonQuery ugyanúgy viselkedik, mint az IDbCommand esetén és az "érintett sorok" – rows affected – számát adja vissza. Mint kiderült nem egészen…)

Aztán tovább ásva megtaláltam a System.Data.Linq.SqlClient.SqlProvider osztályt, ahol az ExecuteNonQuery egy SqlExecuteResults típussal tér vissza, ami implementálja az IExecuteResults interfészt. Már csak az maradt kérdés, hogy hogyan kap értéket a ReturnValue tulajdonság? A konstruktorból kiderül, hogy a @RETURN_VALUE paramétertől.

Na de ki beszél itt SQL paraméterről, hiszen ennek a tárolt eljárásnak nincs se input, se output paramétere! Nosza kérdezzük meg, mi megy az adatbázisba, amihez használhatjuk a DataContext.Log tulajdonságát. Az eredmény:

    EXEC @RETURN_VALUE = [dbo].[GetCustomerCount]
    -- @RETURN_VALUE: Output Int (Size = 0; Prec = 0; Scale = 0) NOT NULL []

Hűha! Irány az SQL Server Books Online, hányféleképpen lehet beállítani egy tárolt eljárás visszatérési értékét? Nekem SELECT-tel nem sikerült, maradt a RETURN, amihez át kellett írni a tárolt eljárást, például így:

    DECLARE @Result int
    SELECT @Result = Count(*) FROM Customers
    RETURN @Result

Ez engem zavar, ezért kedves SQL guruk, mondjatok valami egyszerűbbet!

Ami pedig az MSDN-t illeti, egyetlen rövid mondat rengeteg időt megspórolhatott volna. Persze, tudom, béta termék, meg ez csak egy metódus a sok közül, de akkor is. Gondoltam egyet és megkeresetem az ExecuteMethodCall oldalát az MSDN Wikiben: abban reménykedtem, hogy ha ide megírom a felfedezéseimet, az majd segít másoknak és valamilyen formában bekerül az RTM dokumentációba. Nos, erről lepattantam, egyrészt mert úgy látszik a béta dokumentációhoz nem lehet hozzáírni, másrészt pedig ennek a metódusnak a leírása angolul nem található meg, csak japánul smile_sniff Ez remélem átmeneti bug és nem feature smile_sad

 

Technorati tags:

DLINQ: Tárolt eljárások visszatérési típusai (VS trükk)

DLINQ-ből tárolt eljárásokat hívni pofon egyszerű: ráncigálni kell egy kicsit a VS dizájnert, míg ki nem pottyan egy metódus, aminek a szintaktikája éppen a tárolt eljáráséval egyezik meg. Az viszont nagyon nem mindegy, hogy a dizájnerben hogyan kattintgatunk, és mivel a jelenlegi bétában nem sok visszajelzést kapunk, íme egy trükk!

Példaként használjuk a Northwind adatbázist, és akarjuk meghívni a következő nagy bonyolultságú tárolt eljárást:

    CREATE PROCEDURE dbo.GetCustomersInCity
    (
        @City nvarchar( 15 )
    )
    AS
        SELECT * 
        FROM Customers
        WHERE City = @City

Van tehát egy string bemenő paraméterünk, válaszként pedig a Customers tábla rekordjait adjuk vissza. Ennek burkolására úgy készíthetünk DLINQ-es metódust, hogy a Visual Studio "Orcas" O/R Designerében dolgozgatunk, ami akkor jelenik meg, ha Linq to SQL File típusú elemet adunk a projekthez. A dizájner mellé nyissuk ki a Server Explorert, majd ragadjuk meg a tárolt eljárásunkat és dobjuk a dizájner jobb felére, mert az a metódusok helye. Ennek eredménye a következő metódus lesz a DataContext osztályunkban (kicsit kipofozva és leegyszerűsítve):

    [StoredProcedure( Name="dbo.GetCustomersInCity" )]
    public IEnumerable<GetCustomersInCity> GetCustomersInCity( 
        [Parameter( Name="@City" )] string City )
    {
        IQueryResults<GetCustomersInCity> result = 
            this.ExecuteMethodCall<GetCustomersInCity>( 
                this, 
                (MethodInfo) MethodInfo.GetCurrentMethod(), 
                City ); 
        return (IEnumerable<GetCustomersInCity>) result;
    }

A problémát a piros aláhúzott típusok jelentik: a VS lelkesen generált egy új osztályt, aminek olyan tulajdonságai vannak, mint amilyen oszlopokkal a tárolt eljárás visszatér és azt elnevezte GetCustomersInCity-nek. Hagyjuk figyelmen kívül egy pillanatra ennek a típusnak a közel sem bájos nevét és koncentráljunk inkább arra, hogy ez nem a Customer típus, hanem egy ugyanolyan szerkezetű, de másik típus! Ugyanez történik akkor is, ha a dizájnerben már van Customer, a VS nem képes rájönni, hogy a kettő szemantikailag azonos.

Lelkesen felcsaphatjuk a Properties ablakot és örülhetünk, amikor meglátjuk, hogy létezik egy Return Type nevű tulajdonság, aminek az értéke éppen (Auto-generated Type), ám az örömünk hamar elillan, amikor rájövünk, hogy ez bizony read-only.

A rejtély kulcsa abban rejlik, hogy nem mindegy, hova ejtjük a Server Explorerből kifogott tárolt eljárást! Előbb tegyük be a Customers táblát a dizájner bal felére, majd utána fogjuk meg a tárolt eljárást és ne a dizájner jobb felére tegyük, hanem a Customers tábla felett engedjük el az egérgombot. Voila, a fenti kódrészletben az aláhúzott generált típus helyett mindenhol Customer fog szerepelni!

Még egy tanulság a fenti kódrészletből: ha azt akarjuk, hogy a metódus paramétereinek casingjére az FxCop ne panaszkodjon, akkor használjunk a tárolt eljárásokban kisbetűs paraméter neveket, különben kénytelen leszünk módosítani a Parameter attribútum Name tulajdonságát.

 

Technorati tags: