Archive for the ‘ADO.NET’ Category

EF SSDL alapú felhasználói bemenet ellenőrzés

Monday, February 1st, 2010

A következőn töröm a fejem. Az Entity Framework SSDL-jében definiálva vannak az entitás property-k alapvető jellemzői: nullázhatóság, max hossz. Ezeket a GUI-n ki kell kényszeríteni. Nyilván vannak összetettebb validálási szabályok, de most koncentráljunk ezekre az elemiekre.
Utálok minden redundanciát egy rendszerben, ezért azt gondoltam, a szabályokat kiolvasom az EF sémájából, és ebből táplálom meg a validáló részeket, így nem kell törődni az egyszerű validálásokkal, automatikusan működni fognak.

A következő kis kódocska mutatja meg a metaadatok használatát:


o.ForceLoadingSchemas();

var sspaceEntitySets = o.MetadataWorkspace
       .GetItems<EntityContainer>(DataSpace.SSpace)
       .First().BaseEntitySets.OfType<EntitySet>();

foreach (EntitySet es in sspaceEntitySets)
{
    foreach (EdmProperty p in es.ElementType.Properties)
    {
        ReadOnlyMetadataCollection<Facet> facets = p.TypeUsage.Facets;
        Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
        if (facets.Contains("MaxLength"))
        {
            Debug.WriteLine("{0} MaxLenght is {1}", p.Name, (int)facets["MaxLength"].Value);
        }
        Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
    }
}

A ForceLoadingSchemas az ObjectContext partial classában van:


public void ForceLoadingSchemas()
{
    CreateQuery<BusinessEntity>("AdventureWorks2008Entities3.BusinessEntities").ToTraceString();
}

Csinált már valaki ilyet? Van benne valami csapda, amit most nem látok?

SQLCLR deplyment hiba

Wednesday, January 27th, 2010

Error: Incorrect syntax near valami.
Akkor jön elő, ha az SQLCLR assemblyt és benne a függvényeket akarja az VS deployolni. Több oka lehet, most az volt, hogy egy .NET oldalon double-t visszaadó függvény véletlenül így lett deklarálva:


[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek double")]

Mi a hiba benne? SQL Serverben nincs double, csak real és float. Ráadásul a C# float az az SQL real és a C# double az SQL Server float (kb.). :)

Az előbbi helyesen:


[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek float")]

Miért kellett SQLCLR függvényt írni? A futó aggregálások (én legalábbis nem tudok jobbat kurzor nélkül) o(n2)-es algoritmusok, ezt CLR-ben könnyen meg lehet írni o(n)-re. Pl:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class UserDefinedFunctions
{
    internal class Result
    {
        public long RowId;
        public double CumDollarGain;
        public double TopDollarGain;
        public double DollarDrawDown;
    }

    //Input table: create table #trades(RowId long, DollarGain money, other columns possible)
    [SqlFunction(FillRowMethodName = "FillRow", DataAccess = DataAccessKind.Read,
        TableDefinition = "RowId bigint, CumDollarGain float, TopDollarGain float, DollarDrawDown float")]
    public static IEnumerable Cumul()
    {
        using (var conn = new SqlConnection("Context connection=true"))
        {
            using (var cmd = new SqlCommand("select RowID, DollarGain from #trades", conn))
            {
                var res = new List<Result>();
                conn.Open();
                double cumulPrice = 0, topPrice = 0, drawDawn = 0;

                using (SqlDataReader r = cmd.ExecuteReader())
                {
                    int idCol = r.GetOrdinal("RowID");
                    int gainCol = r.GetOrdinal("DollarGain");

                    while (r.Read())
                    {
                        var price = r.GetDouble(gainCol);

                        cumulPrice += price;
                        topPrice = Math.Max(price, topPrice);

                        drawDawn += price;
                        drawDawn = Math.Min(drawDawn, 0);

                        res.Add(new Result
                                    {
                                        RowId = r.GetInt64(idCol),
                                        CumDollarGain = cumulPrice,
                                        TopDollarGain = topPrice,
                                        DollarDrawDown = drawDawn
                                    });
                    }
                    return res;
                }
            }
        }
    }
    public static void FillRow(object obj,
    out long id,
    out double cumDollarGain,
    out double topDollarGain,
    out double dollarDrawDown)
    {
        var r = (Result)obj;
        id = r.RowId;
        cumDollarGain = r.CumDollarGain;
        topDollarGain = r.TopDollarGain;
        dollarDrawDown = r.DollarDrawDown;
    }
};

Sajnos nem lehet átpasszolni a megnyitott SqlDataReadert a két metódus között, ezért kénytelen az ember letárolni az eredményhalmazt. Persze pár ezer sornál ez nem gond.

Parallel.ForEach in action

Thursday, January 21st, 2010

Imádom, nagyon ügyes dolog. Csak azért nem hajtotta ki jobban a procikat, mert már nem győzték a diszkek.
Érdekes problémaként még az jött elő, hogy több mint 100 szálat indított a Parallel, mert úgy érezte ez jó lesz, de az Sql Server connection poolja alapban max. 100 kapcsolatot engedélyez, így egyes szálak bedugultak. Lejjebb lehet venni a szálak számát, vagy feljebb a poolt. Nekem 80 szál elég volt, úgyse győzte már az SQL Server az adatokat.

Jó Entity Framework architektúra cikkek

Tuesday, January 19th, 2010

Hogyan építsünk EF-re többrétegű appot?
1. Anti-Patterns To Avoid In N-Tier Applications
2. N-Tier Application Patterns
3. Building N-Tier Apps with EF4

Jó cikkek.

.NET teljesítményhangolási tapasztalatok 6.

Monday, February 23rd, 2009

A Connection Poolról még pár gondolat. Jó, hogy van ez a pool, meg gyorsít is, örülünk neki, főleg webalkalmazásokban. Olyan appokban viszont, ahol állandóan futnak a dolgaink, mint egy asztali alkalmazás esetén, nem biztos, hogy érdemes mohón nyitni-zárni a kapcsolatot.
A tőzsdei kereskedési algoritmusaim backtestje során sok százezer paraméterkombinációt néz végig a gép, próbálja meghatározni a legvalószínűbb nyerési esélyűt vagy legnagyobb profit/szórással rendelkezőt (ennél jóval bonyolultabb a dolog, de ez most nem tőzsdés bejegyzés lesz).
A DAL-t úgy írtam meg ahogy webalkalmazásokban megszoktam, így minden egyes trade mentésénél nyitottam-zártam a connectiont. Profilerrel megnézve kiderült, hogy a pool ellenére is a futási idő harmada az open/close-zal ment el. Emiatt készítettem kétféle SqlHelper osztályt, az egyik állandóan nyitott kapcsolattal működik, a másik az egyéb helyekre nyit-zár mind eddig.
Összegezve, a connection pool kiváló dolog pillanatokra futó alkalmazásokhoz, mint a webalkalmazások, de nem érdemes erőltetni, ha több százezerszer kell nyitni-zárni a kapcsolatot.

.NET teljesítményhangolási tapasztalatok 5.

Thursday, February 12th, 2009

Szeretjük a connection poolt, de nem mindig.

Tudjuk, hogy manapság nem illik sokáig nyitva tartani az adatbázis kapcsolatot, hanem bemegyünk, kihozzuk a szükséges adatokat, majd lezárjuk a kapcsolatot. Régen (n > 10 év) ez nem volt okos dolog, mert lassú volt a belépés-kilépés. Ezért találták ki a Connection Poolt, amit OLEDB-ben még Session Poolnak hívtak. Egykutya.
Ez arról szól, hogy a kliens oldali adatelérő komponens, pl. az ADO.NET SqlClient providere (System.Data.SqlClient névtér és kölkei) újrahasznosítja a lezárt kapcsolatot. Azaz mi SqlConnection.Close-t mondunk, ő viszont a háttérben nem zárja le a kapcsolatot az adatbázis felé. Ha ezek után újra meg akarjuk nyitni egy kapcsolatot az adatbázis felé, nem hoz létre új kapcsolatot a provider, hanem visszaad egy használtat a poolból. Miért jó ez? Nyilván egy tényleges kapcsolat kiépítése igen költséges, TCP csatorna vagy Named Pipe session felépítése, autentikáció, ecetera. Ezt nagyon sokszor megússzuk, hála a poolnak. A pool 4-8 perc közötti véletlenszerű időközönként azért lezárogatja a használt kapcsolatokat, ott ne zápuljanak.
Nyilvánvaló, hogy felhasználónként egyedi poolok vannak, így egy sysadmin által eldobott kapcsolatot nem adja oda nekem, lópicinek a provider, ez csúnya luk lenne.
Lehet azért így is aljaskodni az új bérlőkkel. Mi van, ha becsatlakozok, kiadok egy use másik adatbázist, majd röhögve lezárom a kapcsolatot? És ha egy-ket set opciót átírok, pl. SET LANGUAGE urdi? Mit lát a következő hívó, aki megkapja a használt kapcsolatot? Hát amit beállítottunk az előző lépésben. Azért ez durván hangzik, ugye? Szerencsére a helyzet az, hogy alapban tiszta környezetet kapunk, köszönhetően annak, hogy a provider pool manager-e végrehajt egy sp_reset_connection hívást mielőtt visszaadná nekünk a használt kapcsolatot. Hogy ez pontosan mit csinál, azt egyszer már leírtam, tessék megnézni a listát.
Azaz bár nem kell minden egyes kéréshez új kapcsolatot megnyitni, azért egy sp hívás csak történik pluszban a háttérben. SQL Profilerben látszik, hogy ez nagyon gyors, de azért ez mégis csak egy hálózati körülfordulás.
És most jön a trükk. Ha teljesen biztosak vagyunk benne, hogy nem tolunk ki a következő hívóval aki megkapja a használt kapcsolatunkat, akkor kikapcsolhatjuk a takarítást. A connection stringbe ezt kell beírni:


Connection Reset=false;

Nézzük meg az alábbi kódot:


SqlConnection conn = new SqlConnection("data source=.;initial catalog=ATS;Integrated security=true;");
SqlCommand cmd = new SqlCommand("sp_who", conn);
cmd.CommandType = CommandType.StoredProcedure;

for (int i = 0; i < 3; i++)
{
    conn.Open();
    cmd.ExecuteNonQuery();
    conn.Close();
}

Az SQL Profilerben ez látszik:


EventClass	TextData	SPID
Audit Login	-- network protocol: LPC
RPC:Completed	exec sp_who 	52
Audit Logout		52
RPC:Completed	exec sp_reset_connection 	52
Audit Login	-- network protocol: LPC
RPC:Completed	exec sp_who 	52
Audit Logout		52
RPC:Completed	exec sp_reset_connection 	52
Audit Login	-- network protocol: LPC
RPC:Completed	exec sp_who 	52
Audit Logout		52

Hoppá, ez nem az, amit vártunk! Csak 1 logint az elején és 1 logoutot a végén, közben meg az sp_resetconnectiont és az sp_who-nkat. Nem működik a pooling?
De. Csibész a profiler.
“The Audit Login event class indicates that a user has successfully logged in to Microsoft SQL Server. Events in this class are fired by new connections or by connections that are reused from a connection pool.”

Ha bekapcsoljuk a profilerben az Event Subclasst, kicsit tisztul a kép:


EventClass	TextData	SPID	EventSubClass
Audit Login	-- network protocol: LPC		1 - Nonpooled
RPC:Completed	exec sp_who 	52
Audit Logout		52	2 - Pooled
RPC:Completed	exec sp_reset_connection 	52
Audit Login	-- network protocol: LPC		2 - Pooled
RPC:Completed	exec sp_who 	52
Audit Logout		52	2 - Pooled
RPC:Completed	exec sp_reset_connection 	52
Audit Login	-- network protocol: LPC		2 - Pooled
RPC:Completed	exec sp_who 	52
Audit Logout		52	1 - Nonpooled

Szóval van pooling, csak a profiler ravaszkodik. Már kivert a víz. Viszont valami ebben zavar. A pooling ugye a kliensen van. Honnan tudja a szerver egy új parancs beérkeztekor az egy új parancsnak számít a kliens részéről a még nyitott kapcsolaton, vagy csak a poolból visszakapott kapcsolaton hajtanak végre egy új parancsot?
Szerintem ő mesterségesen szintetizálja a poolos login-logoutokat, az sp_reset_connection-ök beérkeztekor veszi őket körbe login-logout párossal, a feeling kedvéért. De ez csak tipp.

Kapcsoljuk ki a connection resetet:


SqlConnection conn = new SqlConnection(
    "data source=.;initial catalog=ATS;Integrated security=true;connection reset=false;");

Mit látunk a profilerben? Semmi nem változott! .NET fw. 2.0 felett nem lehet kikapcsolni az sp_reset_connection-t! Ezért nincs már a legújabb doksiban benne ez a beállítás.

Foly. köv.

Logout események a profilerben

Thursday, October 2nd, 2008

Emailben kérdezték tőlem gáz-e, hogy a profilerben a logut eseményeknél jó nagy duration és egyéb értékek vannak.
Röviden: nem.

Hosszabban: a logout események adatai nem túl érdekesek, azok az adott connection által végzett összes munka és várakozás idejét tartalmazzák. Ha lezárod rendesen a kapcsolatokat kliens oldalon, akkor azt várod, hogy a login-logut események rövid időn belül követi egymást, de nem ez történik a
connection pooling miatt. A pooling alapban be van kapcsolva az összes adatelérő komponensben, ezért egy kapcsolat percekig fennáll a kliens és a szerver között, amin sok általad megnyitott kapcsolat pöfögi át a parancsait. Ezért a logoutnál látott érték sok lekérdezés és a közöttük eltelt várakozási idő együttes eredménye.

A pooling=false connection string paraméterrel teszt célból kikapcsolhatod a poolingot, hogy lásd a különbséget. De ezt élőben ne tedd, mert nagyon lelassul tőle az appod, nem véletlenül van bekapcsolva.

Szóval általában nem kell foglalkozni a logout idejével.

Mi az a Dynamic Data?

Wednesday, May 28th, 2008

5 perces kis videó a témáról.

Valami olyasmi cucc ez, amivel rendkívül gyorsan össze lehet rakni CRUD jellegű adatkezelő website-okat, de teljesen testreszabható módon, template alapon, nem sima kódgenerálással. Érdekes megoldás, de nem tudom mennyire lesz majd használható.

Konferenciaanyagok

Thursday, May 8th, 2008

Tegnap lement a konferencia, én ahogy haladtunk előre egyre jobban feloldódtam és jobban éreztem magam. Egyből a délutáni előadással kellett volna kezdenem. :)

Ahogy már írtam korábban sok anyag készült, ami nem került bele a konf folyamába, ezeket hamarosan elkezdem kirakni ide.

Most első lépésként a két előadásom pptjét töltöttem fel.

Update: office 2003 ppt verzió

LINQ vs. SQL teljesítmény

Tuesday, February 12th, 2008

Az előző cikkemmel csak az volt a célom, hogy megmutassam, hogyan lehet megírni ugyanazt a lekérdezést kétféle módon, a teljesítményüket nem is vizsgátam, mivel érzésre egyik se volt túl lassú.

Laci kommentjében a teljesítmény kérdésre hívta fel a figyelmem, hát megnéztem, mi itt a helyzet. Nos, megúszta a LINQ, pedig nem akartam neki reklámot csinálni. Pontosan ugyanolyan teljesítményű lett a két megoldás, ami nem csoda, hisz pontosan ugyanaz az SQL végrehajtási tervet eredményezik! Egyetlen különbség, hogy a sorszámozást LINQ esetén csak procedurálisan tudtam megcsinálni, így a LINQ sql-es kódjában az nem szerepel, de ennek költsége nem is jelent meg a tiszta SQL megoldás végrehajtási tervében, annyira kicsi.

A lényeg tehát, hogy a LINQ nem feltétlenül lassú, sőt, teljesen egyenértékű lehet a hagyományos SQL-es megoldással. De miért is lenne más? Hisz egyszerűen arról van szó, hogy másképp írom le ugyanazt a specifikációt, az egész hatékonysága azon múlik, mennyire okos a LINQ to SQL sqlgenerátora. A jelek szerint egyszerűbb lekérdezésekre tökéletes.

Aki nem hiszi amit írok, nézze meg az alábbi képet (rákattintva nagyban).

LINQ vs SQL teljesítmény

LINQ vs. SQL

Monday, February 11th, 2008

Melyik a szimpatikusabb?


select bucid, score, countMatchedEcid, ROW_NUMBER()
OVER(ORDER BY score DESC) AS 'rank'
from (
select
b.ucid as bucid,
a.ucid as aucid,
sum(w.weight) as score,
count(*) as countMatchedEcid
from EcidNameValueSummary a,
genecidparts b,
weights w
where a.id=b.id
and a.value=b.value
and a.ucid!=b.ucid
and w.id=a.id
and w.allowed=1
and w.limit>=a.countEcids
and a.ucid=@ucid
group by b.ucid, a.ucid
) as hits

var matchQuery = from t in
    (from a in c.EcidNameValueSummaries
    join b in c.genecidparts
    on a.id equals b.id
    join w in c.Weights
    on a.id equals w.id
    where w.allowed == 1
    && w.limit >= a.countEcids
    && a.value == b.value
    && a.ucid != b.ucid
    && a.ucid == ucid
    select new
    {
        bucid = b.ucid,
        aucid = a.ucid,
        score = w.weight1
    })
group t by t.bucid into g
orderby g.Sum(e => e.score) descending
select new RankEntity
{
    Ucid = g.Key,
    Score = g.Sum(e => e.score) ?? 0,
    CountMatched = g.Count(),
    Rank = 0
};

BindingListCollection<RankEntity> ranks =
new BindingListCollection<RankEntity>();
int i = 1;
//It would be cool to do this with linq, but...
foreach (RankEntity e in matchQuery)
{
    e.Rank = i++;
    ranks.Add(e);
}
return ranks;

És emellett, a ROW_NUMBER-t át tudná nekem valaki fordítani LINQ-ra?

DMF, SMO, LINQ példácska

Wednesday, January 23rd, 2008

Nézegettem az SQL 2008 könyvet, és az ottani példa alapján kedvem támadt kipróbálni a LINQ-t. Tetszik, nem tudom valódi appokban tényleg jó-e, de babrálni vele érdekes.


using System;
using System.Linq;
using Microsoft.SqlServer.Management.Dmf;

class Program
{
    static void Main(string[] args)
    {
        ConsoleColor origColor = Console.ForegroundColor;

        var fc = from f in PolicyStore.Facets
                 orderby f.DisplayName
                 select f;
        foreach (var fi in fc)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Facet {0}:", fi.DisplayName);
            Console.ForegroundColor = origColor;

            var props = from f in fi.FacetProperties
                        orderby f.Name
                        select f;
            int i = 0;
            foreach (var pi in props)
            {
                Console.Write(pi.Name);
                if (i++ > 0)
                    Console.Write(", ");
            }
            Console.WriteLine();
            Console.WriteLine("-----------------------------------");
        }
    }
}

Állítólag ez a DMF nagy durranás az adminoknak, tessék örülni neki és olvasni az ingyenes fejezetet.

Tapasztaltok egy 35ezer tranzakció/mp-es rendszer implementációjából

Thursday, December 13th, 2007

Na, nem én csináltam, sajnos nálunk nem nagyon van ilyen, bár, ha tudtok róla, jelezzétek. Mobilosoknál talán van valami hasonló terheltségű db, de az Oracle.

Szóval, nagyon-nagyon jó post a témában, pár meglepő fordulattal, pl.

Néha nem a legszebb megoldás a legjobb:
“When developing the upsert proc prior to tuning the indexes, I first trusted that the If Exists(Select…) line would fire for any item and would prohibit duplicates. Nada. In a short time there were thousands of duplicates because the same item would hit the upsert at the same millisecond and both transactions would see a not exists and perform the insert. After much testing the solution was to use the unique index, catch the error, and retry allowing the transaction to see the row and perform an update instead an insert.”

A CLR implementáció bukása, talán az interop költségek miatt?

“This app requires that several hundred “items” are passed into the stored procedure for processing as a set. I chose to input the data as comma delimited VarChar(max), originally using Erland’s iterative string2set function to parse string into a set inside the proc. It worked well enough. The plan was to substitute a CLR function near the end of development. When the time came, the CLR function ran blazingly fast on my notebook, but under stress it was painfully sloooooow. I didn’t understand at all why. This is exactly what we were told by Microsoft the CLR was for. Pass string data in, manipulate it, pass it back – no data access – sounds great right? In a few emails to my friend Adam, he told me he had the same issues a while back and the CLR simply could keep up with the marshalling of the VarChar(max).”

Stb. Nagyon jó cikk.

SQL Server 2008 újdonságok 5. - streaming adatok kezelése kliensoldalról

Thursday, December 13th, 2007

Az előző részben elkészült a táblánk, ami streaming adatokat tud tárolni. Itt az ideje pár fotót beleszórni az adatbázisba (illetve a fájlrendszerbe).

Az adatokat beküldhetjük SQL parancsként is, a megszokott TDS csatornát felhasználva. Azonban pont azért rakták ki filerendszerbe ezeket a nagy adatokat, hogy NE TDS-en keresztül, hanem SMB-vel, a windows file megosztásán keresztül érjük el őket. Ez kicsit szokatlan lesz, hisz lesz ugyan SQL INSERT, de lesz sima fájlkezelés is a megoldásban. Mondom, menne sima SQL-lel is, de az nem lenne túl hatékony.

No, a megoldás elég bizarr lesz. Nincs ugyanis managed felület az adatok kezelésére! Legalábbis most, 2007 végén nincs. Natív API van, azt lehet rugdosni .NET-ből, interopon keresztül, ha kell.

A megoldás menete vázlatosan a következő.
1. Hozzákapcsolódunk a szerverhez SqlConnectionnel, tranzakciót indítunk, hozzárendeljük a használandó SqlCommandunkhoz. Eddig semmi új.

2. Beszúrjuk a stream metaadatait, a sima adatokat, egy insert-tel, hagyományos módon, ADO.NET-tel.


insert into Kepek (Id, Name, Photo) values (@Id, @Name, cast ('' as varbinary(max)))

A furcsa üres stringet kasztoló izé azért van a parancsban, hogy megágyazzunk az adatoknak a filerendszerben. E nélkül, ha null maradna az oszlop értéke nem jönne létre fájl a diszken, így a következő lépés se menne.

3. Visszaolvassuk a sorunkhoz tartozó stream mint fájl elérési útját és egy tranzakció azonosítót, ez kell majd a következő lépésben. Mindkettőre van egy új függvény illetve metódus (kiemelve).


select Photo.<strong>PathName()</strong> PathName, <strong>get_filestream_transaction_context()</strong> TranCtx from Kepek where Id = @Id

4. Most jön a bizarrabb rész. Kapunk egy natív függvényt, azzal lehet megnyitni trazakcionálisan a streamünket mint fájlt: OpenSqlFilestream. Mivel ő natív cucc, kell hozzá interop deklaráció, kb. így:


[DllImport("sqlncli10.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle OpenSqlFilestream(
    string FilestreamPath,
    UInt32 DesiredAccess,
    UInt32 OpenOptions,
    byte[] FilestreamTransactionContext,
    UInt32 FilestreamTransactionContextLength,
    LARGE_INTEGER_SQL AllocationSize
    );

[StructLayout(LayoutKind.Sequential)]
public struct LARGE_INTEGER_SQL
{
    public Int64 QuadPart;
    public LARGE_INTEGER_SQL(Int64 quadPart) { QuadPart = quadPart; }
}

Ronda, de az interop már csak ilyen. Örüljünk, hogy működik. Vagy tessék használni C++-t.

A paraméterek értékét az előző pont select-je szolgáltatja, amit egy reader-rel érek el:


byte[] tranCtx = (byte[])reader["TranCtx"];

SafeFileHandle h = OpenSqlFilestream(
(string)reader["PathName"],
DESIRED_ACCESS_WRITE,
0,
tranCtx,
(uint)tranCtx.Length,
new LARGE_INTEGER_SQL(0))

Kapunk egy szép Handle-t, amit az interop réteg egyből be is csomagol egy SafeFileHandle-be, mert azt úgy illik (aki nem hallott a safehandle-ökről, most álljon meg, és sürgősen olvasson utána, nem hosszú a cikk).

5. A Win32 handle jó dolog, de nem kezdünk neki WriteFile API-val baromkodni, hanem kihasználjuk, hogy a FileStream könnyen összebarátkozik valahonnan szerzett File Handle-ökkel:


FileStream fsWrite = new FileStream(h, FileAccess.Write)

6. Most már csak írni kell bőszen az fsWrite-ba. Figyeljük meg, hogy ez a lényege pont a streaming elérésnek, hogy nem összerakunk egy 34 GByte-os ojjektumot, mondjuk byte[]-öt, és odavágjuk a szervenek (eleve, a kliens belehalna ebbe), hanem apránként lapátoljuk be az adatokat. Ráérősen, öregesen. Valahogy így pl.


int readed;
byte[] buff = new byte[4096];
do
{
    readed = fsRead.Read(buff, 0, buff.Length);
    fsWrite.Write(buff, 0, readed);
}
while (readed > 0);

Az fsRead a helyi képre van megnyitva, amit betolunk a szervernek. Teccik látni, kicsi kis darabkákban megy be a hatalmas kép.

Számos kérdés, mint izolációs szint, mi látszik ebből a fájlrendszerben, kihasználja-e a tranzakcionális NTFS-t, stb. merül még fel, ezekkel a következő részben foglalkozok.

Zárásul berakom a teljes kódot, élvezzétek. :)

(Tud valaki valami normális, kész, szép wordpress theme-et, ami úgy van belőve, hogy olyan szélesen formázza meg a szöveget, mint amekkora az ablak mérete? Elkezdtem átszabni ezt a Kubrick theme-et, de ez halál, nekem bonyolult megérteni és átírni. A kódok miatt kellene.)


using System;
using System.Data.SqlClient;
using System.IO;
using System.Data;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace FSTest
{
    class Program
    {
        static void Main(string[] args)
        {
            SqlTransaction tran = null;
            try
            {
                using (SqlConnection conn = new SqlConnection(
                    @"Data Source=.\sql2008;Initial Catalog=FSTeszt;Integrated Security=true;"))
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        conn.Open();
                        tran = conn.BeginTransaction();
                        cmd.Connection = conn;
                        cmd.Transaction = tran;

                        foreach (string f in Directory.GetFiles(@"E:\ment\kepek\Hivatni", "*.jpg"))
                        {
                            //Kép alapadatok beszúrása
                            cmd.CommandText = @"insert into Kepek (Id, Name, Photo)
                                values (@Id, @Name, CAST ('' as varbinary(max)))";
                            cmd.Parameters.Clear();
                            cmd.Parameters.Add("@Name", SqlDbType.NVarChar).Value = Path.GetFileName(f);
                            Guid id = Guid.NewGuid();
                            cmd.Parameters.Add("@Id", SqlDbType.UniqueIdentifier).Value = id;
                            cmd.ExecuteNonQuery();

                            //Maga a kép mentése mint streaming adat
                            cmd.CommandText = @"select Photo.PathName() PathName,
                                get_filestream_transaction_context() TranCtx from Kepek where Id = @Id";
                            using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleRow))
                            {
                                if (reader.Read())
                                {
                                    byte[] tranCtx = (byte[])reader["TranCtx"];

                                    using (SafeFileHandle h = OpenSqlFilestream(
                                        (string)reader["PathName"],
                                        DESIRED_ACCESS_WRITE,
                                        0,
                                        tranCtx,
                                        (uint)tranCtx.Length,
                                        new LARGE_INTEGER_SQL(0)))
                                    {

                                        if (!h.IsInvalid)
                                        {
                                            using (FileStream fsWrite = new FileStream(h, FileAccess.Write))
                                            using (FileStream fsRead = File.OpenRead(f))
                                            {
                                                int readed;
                                                byte[] buff = new byte[4096];
                                                do
                                                {
                                                    readed = fsRead.Read(buff, 0, buff.Length);
                                                    fsWrite.Write(buff, 0, readed);
                                                }
                                                while (readed > 0);
                                            }
                                        }
                                        else
                                        {
                                            int errno = Marshal.GetLastWin32Error();
                                            Console.WriteLine(errno);
                                            Environment.Exit(-1);
                                        }
                                    }
                                }
                            }

                        }
                    }
                    tran.Commit();
                }
            }
            catch (Exception e)
            {
                tran.Rollback();
                Console.WriteLine(e);
            }
        }

        public const UInt32 DESIRED_ACCESS_READ = 0x00000000;
        public const UInt32 DESIRED_ACCESS_WRITE = 0x00000001;
        public const UInt32 DESIRED_ACCESS_READWRITE = 0x00000002;

        [DllImport("sqlncli10.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern SafeFileHandle OpenSqlFilestream(
            string FilestreamPath,
            UInt32 DesiredAccess,
            UInt32 OpenOptions,
            byte[] FilestreamTransactionContext,
            UInt32 FilestreamTransactionContextLength,
            LARGE_INTEGER_SQL AllocationSize
            );

        [StructLayout(LayoutKind.Sequential)]
        public struct LARGE_INTEGER_SQL
        {
            public Int64 QuadPart;
            public LARGE_INTEGER_SQL(Int64 quadPart) { QuadPart = quadPart; }
        }

        [DllImport("kernel32.dll")]
        private extern static void CloseHandle(IntPtr handle);
    }
}

SQL Server 2008 újdonságok 2. - Dátum típusok

Monday, December 10th, 2007

Hát igen, már az SQL Server 2005-ben is úgy volt, hogy lesz DATE és TIME típus. Meg is írták CLR típusként, aztán kidobták, nem illett bele a képbe, kilökte azt a gazdatest.

Most, 2008-ban újra nekiláttak, ezúttal sikerrel (ha élcelődni szeretnék, márpedig miért ne, 10 éve kellett volna ezt meglépni).

No, mi a bajunk a DATETIME és SMALLDATETIME típusokkal.
1. Kicsi az értékkészletünk, 1753 előtt is volt már világ.
2. Kicsi a pontosságuk, a pontosabbnak, a DATETIME-nak is 3 ms a felbontása.
3. Nem kezelnek időzónát.
4. Nincs külön csak dátum és csak idő tároló típus. Eleve, sokszor csak az egyik kell, pl. napra kerekített dátum tárolás, és ilyenkor nem csak könnyebb kezelni a külön tárolt darabokat, de hely se kell neki annyi.

No, lássuk, mit kapunk hát a 2008-ban?

1. DATE típus. 0001-01-01 és 9999-12-31 között működik, és csak 3 byte-ot eszik. Nyilván nap a felbontása.

2. TIME típus. Maximum 100ns felbontású, lehet szabályozni, mennyire legyen pontos. TIME(7) pl. 100ns-os, és 5 byte-os igényel. TIME(0) csak 3 byte, cserébe csak század mp-ig pontos. Még egy példaként TIME(4) 4 byte, és 3-4 digitig pontos (kb. ms-os felbontás).

3. DATETIME2. Az előző kettő hibridje, 6-8 byte kell neki, nyilván az idő tag pontosságától függően.

4. DATETIMEOFFSET típus. A DATETIME2 időzónával kiegészített változata. Stringként így szoktuk leírni: ‘2007-05-08 12:35:29.1234567+12:15, azaz plusz 12 óra 15 perc az időeltolódás.

Ami izgi kérdés, hogyan látszanak ezek a típusok ADO.NET-ből, ráadásul, mit látunk 2.0-ból, és mit a 3.5-ből, ami már fel van készítve az új típusokra?

Először nézzük a 3.5-öt. Az SqlDbType-ban készült négy új érték:

SqlDbType.Date
SqlDbType.Time
SqlDbType.DateTime2
SqlDbType.DateTimeOffSet

Az SQL DATE és DATETIME2 a CLR megszokott DateTime típusára képződik le. A TIME a TimeSpanra, a DATETIMEOFFSET pedig egy új CLR típusra a System.DateTimeOffsetre alakul át. Bővebben itt.

Ez alapján a .NET fw. 2.0 SP1 és 3.0 SP1 is tartalmazza a DateTimeOffset-et. Furcsa mód, reflectorral megnézve a 3.5-ös mscorlibet nem találtam benne ezt az új típust. VS 2008 látja. A 2.0-s mscorlib-ben látja. Bugos a reflector?

Régebbi kliensek szerintem byte[]-ként látják az DateTimeOffSet-et, de most nem tudom tesztelni.

SQL Server 2008 újdonságok 1. - Tábla típusú paraméterek

Friday, December 7th, 2007

A következő néhány hónapban minden nap rászánok kevés időt, hogy az SQL Server 2008-ba belemélyedjek, közben igyekszem dokumentálni a blogomban az újdonságokat. A cikkekben semmilyen tematika vagy sorrend nem lesz, egyszerűen csak magozok az elém kerülő újdonságokból.

Az első új fícsör a tábla típusú paraméterátadás. Ez finom dolog lesz, nem kell stringekbe serializálni a paramétereket, nem kell xmlt használni, hanem egyszerűen át lehet passzintani egy tábla típusú változót a hívott eljárásnak.

Ehhez a CREATE TYPE került felokosításra, ami már nem csak aliasokat tud (SQL Server 7), vagy CLR típusokat (SQL Server 2000), hanem tábla típusokat is (SQL Server 2008). Pl.

CREATE TYPE LocationTableType AS TABLE
(
LocationName VARCHAR(50),
CostRate INT
)

Aztán paraméterként így lehet használni mondjuk spben:

CREATE PROC Ize
(
@par LocationTableType
)

Komplett példa itt látható.

Belül mint egy sima táblát lehet joinolni, stb.

Miért is jó a BOL szerint:

Do not acquire locks for the initial population of data from a client.
Do not cause a statement to recompile.
Provide a simple programming model.
Enable you to include complex business logic in a single routine.
Reduce round trips to the server.
Can have a table structure of different cardinality.
Are strongly typed.
Enable the client to specify sort order and unique keys.

Az első pont mindjárt kíváncsivá tett, hogy lehet ADO.NET-ből feltölteni a tábla paramétert. Zseniálisan egyszerűen, sima DataTable-t kell feltölteni, és már mehet is be a szervernek. Állat.

MS Entity Framework - hej, ráérünk arra még

Sunday, April 29th, 2007

Emlékszem, az egész O/R mapper ügylet még 2002 táján kezdődött. Titkos sugdolódzások voltak: az ms O/R mappert ír (ObjectSpaces), belerakja ingyen a .NET-be, annyi a third party mapper íróknak. Aztán párszor még beígérték a terméket, fw. 2.0, SQL 2005, Vista, most a fw. 3.5. De ez se jött be.

Frans Bouma szerint egyszerű üzleti érdekek miatt tolták el a shipet.

Majd 2008-ban találkozunk vele újra. :)

Jó LINQ forrás

Friday, September 29th, 2006

Betárazom magamnak is tanulásra.

ADO.NET vNext

Wednesday, September 27th, 2006

Nemrég írtam róla némi infót, ebben a blogban sokkal több mindenről lehet olvasni a “jövőről”.