2016. június 28., kedd

NULLIF kontra ISNULL valamint a nullával való osztás kezelése

A NULLIF és az ISNULL ránézésre hasonlónak tűnhetnek, viszont meglehetősen eltérő módon viselkednek.


NULLIF

Szintaxis: NULLIF(expression, expression)

Ha a két kifejezés értéke különbözik, akkor visszaadja az első kifejezést. Ha viszont megegyeznek, akkor NULL-t ad vissza. Egyszerűbb SQL utasítás érhető el vele, mintha a CASE-t használnánk.

NULLIF és CASE összehasonlítására példa az Books Online-ból (BOL):
USE AdventureWorks2012; 
GO 
SELECT ProductID, MakeFlag, FinishedGoodsFlag,  
   NULLIF(MakeFlag,FinishedGoodsFlag)AS 'Null if Equal' 
FROM Production.Product 
WHERE ProductID < 10; 
GO 
 
SELECT ProductID, MakeFlag, FinishedGoodsFlag,'Null if Equal' = 
   CASE 
       WHEN MakeFlag = FinishedGoodsFlag THEN NULL 
       ELSE MakeFlag 
   END 
FROM Production.Product 
WHERE ProductID < 10; 
GO 


ISNULL

Szintaxis: ISNULL(check_expression, replacement_value)

Ha a vizsgálandó első paraméter nem NULL, akkor azt adja vissza, ellenkező esetben a helyettesítő értéket. Kiválóan alkalmas olyan esetekben például, amikor valamilyen alapértelmezett értéket szeretnénk használni, ha egyébként NULL lenne. A COALESCE utasítás egy speciális esetének is felfogható, amikor csak két paramétert kapott és abból kell visszaadnia az első nem NULL értéket.

Jó példa az aggregáló műveletekre szintén az MSDN-ről:
USE AdventureWorks2012; 
GO 
SELECT AVG(ISNULL(Weight, 50)) 
FROM Production.Product; 
GO 

Az aggregáló függvényeknél erősen ajánlott végiggondolni, hogy kellene-e használni, mert ezeknél a függvényeknél, ha legalább egy elem NULL, akkor az eredmény is NULL lesz.


Közös példa: nullával való osztás 
A kettő kombinálására egy jó példa a nullával való osztás kezelése. Először a NULLIF segítségével kezeljük, hogy ha nullával osztanánk, akkor ne dobjon hibát, ekkor ugyanis NULL lesz az eredmény.
SELECT @osztando / NULLIF( @oszto, 0 ) AS value

Majd erre hívjuk meg az ISNULL-t, hogy ilyenkor nullát adjon vissza és kész is:
SELECT ISNULL( @osztando / NULLIF( @oszto, 0 ), 0) AS value

Entity Frameworkből UDT paraméterű tárolt eljárás futtatása

Az Entity Framework alaphangon nem támogatja a saját SQL típust, vagyis a User Defined Type-ot. Ha egy olyan tárolt eljárást szeretnénk importálni az EF-fel, ami UDT típusú paramétert vár, akkor ugyan nem fog hibát dobni, de nem is fogja legenerálni a hozzátartozó kódot.

Ennek áthidalására egy jó módszer az EntityFrameworkExtras nevű NuGettel is elérhető csomag, aminek segítségével típusosan lehet ilyen tárolt eljárást futtatni. A bekötéséhez az alábbi néhány lépés szükséges.

1. UDT létrehozása MS SQL Server adatbázisban

CREATE TYPE [dbo].[TEMP_IDTABLE] AS TABLE(
       [ID] [int] NULL
)
GO

2. Ezt a típust paraméterként használó tárolt eljárás létrehozása

CREATE PROCEDURE [dbo].[DummyStoredProcedure]
       @myValues [Temp_IDTABLE] READONLY
AS
BEGIN
       -- értelmes logika
       SELECT * FROM @myValues
END

3. A használt EF verziónak megfelelő EntityFrameworkExtras hozzáadása a projekthez
  • EF 5: EntityFrameworkExtras.EF5
  • EF 6: EntityFrameworkExtras.EF6

4. Létre kell hozni egy osztályt, ami majd az 1. lépésben elkészült UDT-t fogja reprezentálni

[UserDefinedTableType("TEMP_IDTABLE")]
public class TempIdTable
{
    [UserDefinedTableTypeColumn(1, Name = "ID")]
    public int? ID { get; set; }
}

Természetesen az osztály és a mezők neve bármi lehet, mivel az attribútumokkal lesz beállítva, hogy az adatbázisban mire kell majd leképezni.

5. Az előbbihez hasonlóan a tárolt eljáráshoz kell egy osztály

[StoredProcedure("DummyStoredProcedure")]
public class DummyStoredProcedure
{
    [StoredProcedureParameter(SqlDbType.Udt, ParameterName = "myValues")]
    public List<TempIdTable> MyValues { get; set; }
}

6. Ezek után már csak meg kell hívni az eljárást. Ehhez az EntityFrameworkExtras tartalmaz Extended Methodokat, amik a DbObjectre illetve az ObjectContextre akadnak rá, és olyan objektumokat várnak, amik el vannak látva a StoredProcedure attribútummal.

ObjectContext oc = new ObjectContext("ConnectionString");

var sp = new DummyStoredProcedure
{
    MyValues = new List<TempIdTable>
    {
        new TempIdTable { ID = 1 },
        new TempIdTable { ID = 2 }
    }
};

IEnumerable<int> results = oc.ExecuteStoredProcedure<int>(sp);

2016. június 5., vasárnap

SSMS IntelliSense Cache frissítése

Időnként előfordul, hogy miután létrehoztam, módosítottam esetleg töröltem valamilyen objektumot, az SSMS hibát jelez olyan SQL utasításokban, amik az érintett objektumra hivatkoznak. Annak ellenére, hogy az SQL script sikeresen lefutna, eléggé zavaró, amikor bemutatásnál vagy megbeszélésen piros hibajelzések tarkítják a kódot. Ezt az okozza, hogy az SSMS-ben lévő IntelliSense Cache még nem frissült a változtatás óta.

Szerencsére többféleképpen is ki lehet kényszeríteni, hogy frissüljön:
  • Gyorsgombok segítségével: CRTL+SHIFT+R
  • Menüben kikeresve: Edit / IntelliSense / Refresh Local Cache

HTML FORM-ban egy darab inputbox

A HTML FORM tagnek van egy olyan tulajdonsága, hogy ha csak egy darab, egy soros beviteli mezőt tartalmaz (<input type="text"/>), akkor az ENTER lenyomására mindig lefut a SUBMIT, függetlenül attól, hogy van-e rá beállítva alapértelmezett gomb.

Célszerű az alábbiakat ellenőrizni, ha az ENTERre olyankor is újratölti az oldalt, amikor nem kellene:
  • csak egy darab beviteli mezőt tartalmaz a FORM
  • van submit típusú gomb (<input type="submit"/>)
  • van BUTTON tag és annak milyen értékű a type attribútuma, mert a submit az alapértelmezett
  • van valami JavaScript függvény beregisztrálva a FORM-ra, a beviteli mezőre vagy esetleg az egyik gombra a FORM-hoz kapcsolódóan

2016. április 3., vasárnap

Visual Studio 2010 eltávolítása

Elérkezett a pillanat, hogy megválok a rég nem használt VS2010-től. Gondoltam, egyszerűen fogom az uninstallert és ráeresztem, de nincs ilyen. Mivel nem volt kéznél a telepítője, így jó ötletnek tűnt, hogy ezt követően a Programok eltávolítása menüpontban keressek ki és töröljek le minden VS2010-re utaló alkalmazást. Meglepetten fogadtam, hogy a VS mappája továbbra is ott vigyorgott.

Létezik egy Visual Studio 2010 Uninstall Utility alkalmazás, ami pont erre való, és az alábbi üzemmódokban képes működik:

Default (VS_2010_Uninstall-RTM.ENU.exe)
Minden "top level" terméket töröl, ami a 2010-es verzióhoz tartozik és az ezeket támogató komponenseket. Nem bántja a korábbi verziókkal megosztott komponenseket, illetve a rendszerszintű frissítéseket, pl a .NET 4.0-t.

Full (VS2010_Uninstall-RTM.ENU.exe /full)
Ebben a módban már a korábbi verziókkal megosztott komponenseket is eltávolítja, ami gondot okozhat  a telepített korábbi VS verziókban. A .NET 4.0-t ez sem bántja.

Complete (VS2010_Uninstall-RTM.ENU.exe /full /netfx)
Minden VS2010-hez tartozó alkalmazást eltávolít, beleértve a .NET4.0-t is.

A leírás alapján a default módot választottam, ami immáron ténylegesen le is törölte a VS maradványait.


Nem sokkal később az újabb VS verzióm puffogott, a .NET4-es projektjeimet nem tudta betölteni és az alábbi opciókat ajánlotta:
  • .NET4.5-re áttérés
  • .NET4 letöltése és telepítése
  • projekt kihagyása
A VS-ben nem lehetett tovább kiválasztani a .NET4 egyik verzióját sem. Ám legyen, gondoltam én, feltelepítem újra, viszont a telepítő azt mondta, hogy az operációs rendszeremnek eleve része, valamint ez vagy egy újabb verzió már telepítve van, ami miatt nem hajlandó települni. Ezek részben igazak is, mert a .NET 4.5 valóban megvan, és elvileg része volt az OS-nek is, most pedig már nem az.

A VS2013 "javítás" funkciója megoldotta a helyzetet és nem kellett újratenni a teljes Windows-t.

Tanulság az egészben, hogy ha egy Microsoft termék mellett nem található uninstall.exe, akkor célszerű a telepítőjével próbálkozni, mivel abban lesz benne. Másfelől, ha nincs kéznél a telepítő, akkor nagy valószínűséggel külön mégiscsak beszerezhető egy kisméretű uninstaller.


2016. április 1., péntek

SSRS-ben #Error a számított érték helyén

Megesett, hogy egy riporton szerettem volna megjeleníteni egy kifejezéssel előállított értéket, de a riporton csak egy #Error jelent meg a helyén. A naplófájlban lévő hibaüzenet sem volt túl beszédes:

ERROR: Throwing Microsoft.ReportingServices.ReportProcessing.ReportProcessingException: , Microsoft.ReportingServices.ReportProcessing.ReportProcessingException: The specified operation is not valid. ;

Ilyen hibánál általában az a baja, hogy eltérő típusú adatokat szeretnénk aggregálni. Esetemben egy Sum(IIF( exp, érték1, érték2)) okozta a gondot, ahol bizonyos csillagállás mellett nem egyezett meg a két érték típusa, mert két különböző számtípusú volt.

2016. február 29., hétfő

Hibakeresés SSRS-ben

A napokban több feladatom is volt SSRS használatával és megosztanám néhány problémás eset tanulságát.

1) Textbox helye nem frissül
Egy újabb riport elkészítése során egy már létezőből indultam ki, jelentős részét átmásolva, felhasználva. Az egyik helyen két textboxot próbáltam egymás alá igazítani, de bármennyire is növeltem a távolságot a kettő között, a generált képen mindig egy sornyira volt a két szöveg. Ekkor fedeztem fel, hogy a textboxnak van egy olyan tulajdonsága, hogy CanShrink, ami pont azt csinálja, hogy csökkentheti a magasságát, ha kevesebbet igényel a szöveg. Ez a tulajdonság, alapértelmezetten ki van kapcsolva, de esetemben, mivel másolt kóddal dolgoztam, engedélyezve volt. Végül én is bekapcsolva hagytam, mert jól jött ez a funkció, de ettől függetlenül nem árt tudni róla, hogy létezik és magyarázatot adhat arra, hogy miért nem pont úgy néz ki a generált kép, mint ahogyan a tervezőnézetben.


2) Subreportok beágyazása
A másik esetben a lényeg az volt, hogy két riportot kellett volna előállítani úgy, hogy az egyik a másikhöz felolvasott adatok egy részével paraméterezve működött. Úgy álltam neki, hogy egy container reportra felhelyeztem két subreportot és külön-külön behivatkoztam a már létező, kész és működő riportokat. 

Hibakeresés - subreport
Ekkor jött az első hibaüzenet, ami nem volt túl beszédes: Error: The subreport could not been shown.
Az SSRS saját logja már legalább azt megmondta, hogy az egyik subreporttal van baja:
[rsWarningExecutingSubreport] Warnings occurred while executing the subreport ‘Subreport1’. [rsNone] One or more parameters required to run the report have not been specified. [rsNone] One or more parameters were not specified for the subreport, 'Subreport1', located at: /Child. \\blahblahblah\Parent.rdl

A naplófájl alapértelmezetten itt található:
C:\Program Files\Microsoft SQL Server\MSRS[X].MSSQLSERVER\Reporting Service\LogFiles

A hibaüzenet alapján átvizsgáltam a paramétereket, leellenőrizve, hogy megfelelő típusút adok mindenhova illetve, hogy nincs-e elírás. Mivel ezen téren nem találtam hibát, konstans értékeket kötöttem be, mivel ezzel ki behatárolhattam, hogy a beállítandó értékek hibáznak-e vagy máshol lehet a hiba. A tesztelésnél így is jött a hibaüzenet. Ezt követően a subreportot módosítottam úgy, hogy minden paraméterének adtam alapértelmezettet értéket, és a parentben semmit sem adtam át.

Ekkor belefutottam abba, amiről már korábban is írtam, hogy ha megváltoztatom egy paraméter tulajdonságát, akkor azt csak úgy nem lehet frissíteni egy redeploy alkalmával. Ezt követően a Visual Studio report preview-ja is cache-elt, amit könnyedén elintéztem, erre már van egy toolom, amiről itt írtam.

Mivel a riport probléma nélkül betöltődött az alapértelmezett értékkel, így egyesével elkezdtem beállítani konstans értékekkel a paramétereket a parentből a subreportnak és figyeltem, hogy mikor romlik el. Egy olyan paraméternél akadt meg, ami a subreporton text típusú, egy másik paraméter alapján egy datasetből vette az értékkészletét, ráadásul integert. Pusztán annyira szolgált ez a text paraméter, hogy egy szöveget felolvassunk az adatbázisból az egyik paraméter alapján. Az lett a megoldás, hogy felszámoltam ezt a text paramétert és a datasetből közvetlenül használtam a riporton a szöveget.

Hibakeresés - fejléc/lábléc
Miután végre sikeresen betöltődött mindkét riport, akkor jött a felismerés, hogy nem látszik a fejléc/lábléc a subreportról. Az MSDN szerint tervezetten nem jelenik meg egy subreport ezen része a parenten. 

Léteznek különböző ilyen-olyan megkerülő módszerek arra, hogy mégiscsak látszódjanak, de eléggé gányolásnak tűnnek számomra, szemben azzal, mintha csak fel kellene tenni két subreportot egy containter parentre, bepipálni, hogy látszódjanak és máris megfelelően beágyazná őket a subreporthoz tartozó oldalakon.
  1. A child legyen felbontva 3 subreportra: header, body, footer, és mindegyik a child body részébe legyen beillesztve. Így emiatt a parent teljes értékűen meg fogja jeleníteni azokat is, már a lefejlesztése sem kis idő és pénz és utána a karbantartás költsége is az egekben fog járni, nem beszélve a potenciálisan behozott hibalehetőségekről.
  2. A child fejléc/lábléc eleme legyen külön hozzáadva a parentben is, majd pedig az oldalszám függvényében legyen állítva a láthatósága. Ehhez valamennyire részletes leírás itt található. Önmagában egyszerűnek tűnhet, viszont arra nem találtam semmi megoldást, hogy dinamikusan hogyan lehetne kezelni az oldalszámokat és azok alapján az egyes fejlécek láthatóságát.
Összegzés
Végeredményben arra jutottam, hogy C# kódból egymás után hívom a két riport generálását úgy, hogy kódban előre felolvasom a közös adatokat és azzal paraméterezem őket, ezzel csökkentve a redundáns adatmozgatást.



Szerintetek létezik valamilyen használható megoldás arra, hogy több subreportot beágyazva egy containerbe meg lehessen jeleníteni a saját fejléc/lábléc részeiket? Másként szólva lehetséges teljes értékű merge funkciót kicsikarni a reporting services-ből?