Archive for the ‘Adatbázisok’ Category

Gondolatok a queue alapú kliens-szerviz kommunikációhoz

Thursday, June 16th, 2011

Az előző post kommentjei alapján (amit nagyon köszönök mindenkinek) nem kaptam sok bátorítást az aszinkron, queue alapú, kérés-választ különválasztó gazdag kliens - szerviz kommunikációhoz, úgy tűnik senki nem csinált ilyet, így nem akarok úttörő lenni a témában.
Ehhez még hozzájárul, hogy hétvégén méregettem az MSMQ teljesítményét. Azért ezt, mert a WCF is erre épít, és pl. az NServiceBus is.

A tesztkód 1.5kByteos üzeneteket rak át egyik sorból a másikba. Az ötlet innen jött, csak többszálasítottam.

A tesztkód:


using System;
using System.Diagnostics;
using System.Messaging;
using System.Threading;

namespace MsmqTran
{
    class Program
    {
        private const int NumberOfTests = 1000;
        private const int MaxDop = 10;
        private static readonly ManualResetEvent[] WaitForEmpty = new ManualResetEvent[MaxDop];

        static void Main()
        {
            var q1 = new MessageQueue(@".\private$\test_queue1");
            var q2 = new MessageQueue(@".\private$\test_queue2");

            Console.WriteLine("Filling source queue...");
            var b = new byte[1500];
            using (var msmqTx = new MessageQueueTransaction())
            {
                msmqTx.Begin();
                for (int i = 0; i < NumberOfTests; i++)
                {
                    q1.Send(b, msmqTx);
                }
                msmqTx.Commit();
            }

            q2.Purge();
            Console.WriteLine("Starting to move data from source queue to destination queue");

            var sp = Stopwatch.StartNew();

            for (int i = 0; i < MaxDop; i++)
            {
                WaitForEmpty[i] = new ManualResetEvent(false);
                ThreadPool.QueueUserWorkItem(o => ProcessMsg(q1, q2, (ManualResetEvent)o), WaitForEmpty[i]);
            }

            WaitHandle.WaitAll(WaitForEmpty);

            Console.WriteLine("Duration: {0}ms, throughput: {1:F0} messages/s", sp.ElapsedMilliseconds, 1000.0 * NumberOfTests / sp.ElapsedMilliseconds);
        }

        private static void ProcessMsg(MessageQueue q1, MessageQueue q2, ManualResetEvent w)
        {
            while (true)
            {
                using (var msmqTx = new MessageQueueTransaction())
                {
                    msmqTx.Begin();

                    Message message;
                    try
                    {
                        message = q1.Receive(TimeSpan.FromMilliseconds(0), msmqTx);
                    }
                    catch (MessageQueueException e)
                    {
                        Console.WriteLine(e);
                        w.Set();
                        break;
                    }

                    q2.Send(message, msmqTx);

                    msmqTx.Commit();
                }
            }
        }
    }
}

A gépemen 50 tran/sec-kel megy 1 szálon, és 200 fölé nem nagyon megy. Jó, ez laptop, de relációs adatbáziskezelővel (sql server és oracle is fut a gépen) több ezer tran/seccel mennek a dolgok. Szóval ez elég gázosan lassú. Emellett csúnya leállásokról is írnak a blogokban, amikor beáll az msmq.

Marad a aszinkronított WCF egyelőre, csak a szerverről visszafelé hívásokat tervezem queue alapon megcsinálni, WCF msmq bindinggal. Így tudom értesíteni az appokat polling nélkül. Erre 3 okom van most:
1. Az offline (disconnected) pessimistic lock feloldódott, lehet szerkeszteni valamit.
2. Frissíteni kell a kliens cache-ben valamit.
3. Email jellegű üzenetküldés az appok között.

Köszönöm még egyszer az építő javaslatokat.

Alternatíva a gonosz EAV-val szemben?

Thursday, May 19th, 2011

Az üzleti igény egy olyan adatbázis struktúra létrehozása, amelyben az előre definiált táblák adatai mellé a kusztomerek saját maguk is fel tudnak venni plusz adatokat, anélkül, hogy ehhez az adatbázistáblákat és az alkalmazást módosítani kellene. Sajnos nem csak letárolják ezeket, de még keresni is akarnak egyesekre. Azaz egyfajta open schemat kellene megvalósítani. Erre jó lehet az Entity-Attribute-Value megoldás, de úton-útfélen gonosznak van kinevezve. Értem én, hogy szar belőle lekérdezni, marha nagy lehet, ha egyszerűen csinálják meg nem típusos (én minden típushoz akarok egy oszlopot létrehozni), de milyen használható alternatíva van vele szemben?
Olvastam a serialized blobot, pl. egy xml oszlopba rakjuk a plusz dolgokat. Elvileg lehet xqueryzni, meg indexelni, de érzésre ez se egyszerű, se gyors nem lesz.
A sparse colum esetén séma módosítással jár az új adat definíciója. Ez egyrészt nem tetszik secu okokból (alter table kell hozzá), másrészt elég gáz szervizelni az alkalmazást, ha 50 ügyfél saját oszlopokkal bővítheti a tábláinkat.
Szóval, mi a jó a feladatra? Nekem még mindig az EAV tűnik a legkezelhetőbbnek.

SQL Server - Oracle könyv

Wednesday, April 27th, 2011

Meglegyintett a sötét oldal, ezért kénytelen leszek kicsit érteni az Oracle-höz is. :)

Tud valaki jó könyvet, ami valamiféle SQL Server - Oracle turbó átképzésről szól? Nem akarom a create table-nél kezdeni, ikább valami diff könyv kellene.
Persze, lehet ez nem ilyen egyszerű, el kell kezdeni valami Essential Oracle könyvvel.

Érdekes SQL programozási hibák

Wednesday, April 27th, 2011

Előző héten két érdekes hibát láttam ügyfeleknél.
Az egyikben láttam, hogy a lekérdezés végrehajtási tervében konverzió van, datetime -> integer, emiatt scan volt seek helyett, azaz lassú volt a lekérdezés.
Ami fura volt ebben, hogy egy izeID oszlop volt összehasonlítva egy datetime értékkel. Az ID-k tipikusan intek, megnézve a táblát tényleg az volt. Volt egy lokális változó, ami copy-paszta miatt datetimera sikerült int helyett, és ezzel írták tovább a where szűrést. A lekérdezés már 2 éve ment élesben. :)
Ami érdekes volt benne, hogy funkcionálisan jól működött, a számok szépen konvertálódtak dátumokká és vissza, nem volt vele gond, csak lassú volt az egész. Bizarr hiba.

A másik esetben egy ilyen lekérdezés volt:


declare @a int
while valami
begin
  select @a = oszlop from tabla where ...
end

Furcsa volt, hogy a @a furcsa módon tartalmazott értéket akkor is, amikor nem érintett sort a select a where miatt.
Jobban belegondolva ez nem is fura, hisz a select NEM ad értéket a változónak, ha nem érint sort, és NEM is nullázza ki. Első iterációkor a @a null, hisz nem kapott értéket, a második iterációnál meg benne volt az első futás eredménye, ami már nem null volt, bár azt várták. Nem nagy hiba, de időrabló tud lenni.

option(recompile) nem mindig működik

Thursday, April 14th, 2011

Hirtelen egymás utáni napokon 3 cégnél égetett be az SQL Server 2005-től létező option(recompile), miután nem működik.

A hint célja az lenne, hogy a konkrét paraméterértékek ismeretében kérünk egy újrafordítást, így az optimizer ki tud dobni felesleges ágakat a lekérdezésből, ezzel nagyon hatékony terveket tud létrehozni egyes speciálisabb lekérdezésekhez.
Pl. gyakori, hogy a paraméterre csak akkor kell szűrni, ha nem null, ha null, jöjjön vissza minden sor:

… where @param is null or oszlop = @param

Ez alapban scan lenne, lassú. Az option(recompile) hatására azonban ha a @param null, akkor teljesen kiesik a sor, ha nem null, akkor meg leegyszerűsödik erre:

… where oszlop = @param

Ez meg már jó kis gyors seek lesz, megfelelő index esetén.

Mi itt a gond? Csak az, hogy ez sok verzión NEM működik. Pedig működött. :)

Az ok a következő. SQL 2005-ben még nem működött. 2008-ra megcsinálták, ment, csakhogy egy igen durva bugot is beleszereltek: ha többen hajtanak végre ilyen hintelt lekérdezést, akkor összekeveredhetnek az eredményhalmazok. Ez az innye, bazmeg típusú bug. Gyorsan ki ki kommenteltek pár sort, mivel megvarrni meg nem olyan egyszerű, mint elrontani. Így ez most nem megy az újabb SQL Servereken.
Erland barátunk szerint az R2 RTM NEM tartalmazza még a javított verziót (magyarul helyesen működik, nem nem gyorsít).
Viszont ez az R2 CU1 már javítja.
Ezen doksi szerint már 2008-hoz is van javítás, a CU5-ben.

Már csak le kéne cseréni a cégeknek a 2005-öt. :)

Insert… select … minimálisan logolt módú az SQL 2008-ban

Thursday, March 3rd, 2011

Ha az insert table with (tablock) módon írjuk a lekérdezést, és az heap, akkor bulk insert lesz a sima insert. Nem kevés idő megtakarítást jelent ez.

Adding External References to SQL CLR Projects

Friday, February 25th, 2011

Ha egy SQLCLR eljárásból kell meghívni valamilyen külső komponenst.
A trükk az, hogy a külső assembly-t is be kell telepíteni az SQL serverbe CREATE ASSEMBLY-vel, mivel az csak onnan hajlandó betölteni assemblyket (pár fw. alap dllt kivéve).

SQL Server Profilerben TSQL hibák vizsgálata

Wednesday, February 16th, 2011

SQL tanfolyamot tartok egy cégnél (.NET 4-et és Design Patternst meg egy másiknál :). Az oktatások arra jók, hogy mindig rájövök mit nem tudok, így aztán van módom újat tanulni. Azt meg általában szeretek. :)

Na szóval, ha a szerver oldalon történik egy exception valamilyen sql parancs végrehajtási hiba következtében, akkor ez szépen látszik a profilerben, csak az nem, mi váltotta ezt ki.
Most lapozgattam az Inside Microsoft SQL Server 2005: Query Tuning and Optimization könyvet, és ott belefutottam a megoldásba.
Nem csak az Errors And Warnings: Exception eseményt kell bekapcsolni, hanem a User Error Message-et is.
.
A képen látható, az EventSequenceből szépen összerakható az események egymásutánisága.
Ez megint egy olyan apró kérdés volt, amit 5 perc guglizás megold, de hát nem ilyenekkel van tele az ember feje?

SQLXMLBulkload

Monday, February 7th, 2011

Már el is felejtettem, hogy van ilyen. Öregszem. :)

How to Share Data Between Stored Procedures

Thursday, January 6th, 2011

Felírom magamnak, hasznos lehet.
http://www.sommarskog.se/share_data.html

Defensive Database Programming ingyenes könyv

Saturday, May 15th, 2010

Érdekesnek ígérkezik, ingyenes, letölthető könyv.

Szótöredék keresés SQL Serveren - furcsának ható hiba

Thursday, May 13th, 2010

Az alábbi kérdezték tőlem pár perce:


SELECT [text]  FROM [table1] WHERE [text] like '%rekes%'

A rekesz szót nem találja meg, ha magyar a collation az oszlopon. Ez természetes, a kettős betűket ezzel a logikával kezeli a szerver, azaz egy betűnek tekinti őket. A where-be kell egy collation cast, mondjuk latin1-re, amiben nincsenek kettős betűk. Mondjuk ezután nem fog indexet használni a szerver, de a kezdő % miatt eleve nem használva.
Az alábbi példában az egyikkel collationnel megtalálja, másikkal nem.


SELECT [text]  FROM [table1] WHERE [text] like '%rekes%'
--  collate Hungarian_CI_AS
collate Latin1_General_CI_AS

Lehet csinálni indexelt, számított oszlopot is más collationnel, és arra szűrni, az gyorsabb lesz.

LogParser példák

Wednesday, May 12th, 2010

Durva gyors jószág ez a logparser, SQL Server alatt alaposan alá kellene indexelni az adatoknak, hogy ennyire gyorsan kezeljük őket.
Jó példák hozzá, meg itt és itt.

64 bites Office 2010 és unit tesztek

Saturday, May 8th, 2010

Futtatni akartam pár régebbi unit tesztemet, de nem mentek, azt mondta, nem találja a Microsoft.ACE.OLEDB drivert. Tévesen több helyen azt láttam, hogy azt kell beírni, hogy Microsoft.ACE.OLEDB.14.0, de nem, a registryben is a régi, 12-es verzió van. Megnéztem, az InprocServer32 a C:\Program Files\Common Files\Microsoft Shared\OFFICE14\ACEOLEDB.DLL-re mutat, ami a manifestje alapján 64 bites C runtime dlleket használ, szóval tényleg van 64 bites access driver. (Az InprocServer32 név jó nagy fiaskó, minden ilyen bedrótozott verzió visszaüt később, mint a 16 bites shortParam, ami valójában 32 bites int).

Sejtettem, hogy 64 bit - 32 bit probléma van, de nem jöttem rá mi az oka, míg a tesztben ki nem írattam a teszt futtató bitszámát: Environment.Is64BitProcess == false.
Ekkor csaptam a homlokomra, hogy alapban 64 bites gépen is 32 bites processzben futnak a tesztek. De szerencsére át lehet állítani.
Ti ne töltsetek el 1 órát ilyen marhasággal, emlékezzetek erre. :)

SQL Server 2008 R2 RTM

Monday, May 3rd, 2010

Fenn van az MSDN-en, vigyük még meleg.

Entity Framework 4 többrétegű appokban

Tuesday, April 20th, 2010

Játszottam kicsit az EF4-gyel. Az alábbi kód egy n rétegű app adatmozgását szimulálja a WCF xml szerializálóját használva. Mindhárom template-tel kipróbáltam, alább láthatóak az adatmozgások.
A tesztkód messze nem korrekt, de kiindulópontként további vizsgálatokhoz elfogatható:


using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;

namespace POCO1
{
    class Program
    {
        static void Main()
        {
            Department d;
            using (var e = new SchoolEntities())
            {
                e.ContextOptions.ProxyCreationEnabled = false;
                e.ContextOptions.LazyLoadingEnabled = false;
                d = e.Departments.Include("Courses").Single(dep => dep.DepartmentID == 1);
                Console.WriteLine("{0}", d.Name);
                Console.WriteLine("---------------------");
                foreach (Course c in d.Courses)
                {
                    Console.WriteLine("{0}", c.Title);
                }
                e.Detach(d);
            }

            var ser = new DataContractSerializer(d.GetType());
            //var ser = new DataContractSerializer(d.GetType(),
            //null, 50000, true, true, null, new ProxyDataContractResolver());

            using (var s2c = new FileStream(@"c:\temp\Server2Client.xml", FileMode.Create, FileAccess.ReadWrite))
            {
                //1. Server küld kliensre
                ser.WriteObject(s2c, d);
                s2c.Position = 0;
                //2. Kliens deserializál
                var clientSideDep = (Department)ser.ReadObject(s2c);

                //Csak ST
                //bool ce = clientSideDep.ChangeTracker.ChangeTrackingEnabled;

                //3. Kliens módosít
                clientSideDep.Name += "a";

                using (var c2s = new FileStream(@"c:\temp\Client2Server.xml", FileMode.Create, FileAccess.ReadWrite))
                {
                    //4. Kliens visszaküld
                    ser.WriteObject(c2s, clientSideDep);
                    c2s.Position = 0;

                    //5.Server deserializál
                    var sentBackDepartment = (Department)ser.ReadObject(c2s);
                    using (var e = new SchoolEntities())
                    {
                        //6. Server visszamódosít

                        //Normál entitás
                        e.Departments.Attach(sentBackDepartment);
                        e.ObjectStateManager.GetObjectStateEntry(sentBackDepartment).SetModified();
                        e.ObjectStateManager.GetObjectStateEntry(sentBackDepartment).SetModifiedProperty("Name");

                        //POCO
                        //e.Departments.Include("Courses").Single(dep => dep.DepartmentID == 1);
                        //e.Departments.ApplyCurrentValues(sentBackDepartment);

                        //Self-tracking entity
                        //e.Departments.ApplyChanges(sentBackDepartment);

                        e.SaveChanges();
                    }
                }
            }
        }
    }
}

EF alapobjektumok szerviz => kliens:


<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <EntityKey z:Id="i2" xmlns="http://schemas.datacontract.org/2004/07/System.Data.Objects.DataClasses" xmlns:a="http://schemas.datacontract.org/2004/07/System.Data">
    <a:EntityContainerName>SchoolEntities</a:EntityContainerName>
    <a:EntityKeyValues>
      <a:EntityKeyMember>
        <a:Key>DepartmentID</a:Key>
        <a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:Value>
      </a:EntityKeyMember>
    </a:EntityKeyValues>
    <a:EntitySetName>Departments</a:EntitySetName>
  </EntityKey>
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineering</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

EF alapobjektumok kliens => szerviz:


<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <EntityKey z:Id="i2" xmlns="http://schemas.datacontract.org/2004/07/System.Data.Objects.DataClasses" xmlns:a="http://schemas.datacontract.org/2004/07/System.Data">
    <a:EntityContainerName>SchoolEntities</a:EntityContainerName>
    <a:EntityKeyValues>
      <a:EntityKeyMember>
        <a:Key>DepartmentID</a:Key>
        <a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:Value>
      </a:EntityKeyMember>
    </a:EntityKeyValues>
    <a:EntitySetName>Departments</a:EntitySetName>
  </EntityKey>
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineeringa</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

POCO szerviz => kliens:


<Department xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineeringa</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

POCO kliens => szerviz:


<Department xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineeringaa</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

Self-tracking entity, szerviz => kliens:


<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <ChangeTracker z:Id="i2">
    <ExtendedProperties/>
    <ObjectsAddedToCollectionProperties/>
    <ObjectsRemovedFromCollectionProperties/>
    <OriginalValues/>
    <State>Unchanged</State>
  </ChangeTracker>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineeringaaaaaaaa</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

Self-tracking entity, kliens => szerviz:


<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <Administrator>2</Administrator>
  <Budget>350000.0000</Budget>
  <ChangeTracker z:Id="i2">
    <ExtendedProperties/>
    <ObjectsAddedToCollectionProperties/>
    <ObjectsRemovedFromCollectionProperties/>
    <OriginalValues/>
    <State>Modified</State>
  </ChangeTracker>
  <Courses/>
  <DepartmentID>1</DepartmentID>
  <Name>Engineeringaaaaaaaaa</Name>
  <StartDate>2007-09-01T00:00:00</StartDate>
</Department>

Érdekes, hogy a módosítás ténye csak a ST-ben látszik, még az eredeti entity-sben sem. A POCO-tól nem is vártuk persze.
Később még majd foglalkozok bővebben a témával. Aki játszani akar vele, hozza létre a School EF példaadatbázist, azon lehet futtatni.

A tegnapi SQL Server 2008 R2 előadásom anyagai

Tuesday, April 13th, 2010

Többen kérdeztétek, pptben benne vannak a linkek a térképes demóm adataihoz és a konverziós eszközökhöz is.
Mellékeltem még a Running Aggregate SQL és CLR példát (kurzorral is, azt nem mutattam), a trade riportot az új chartocskákkal.
Letöltés innen (4 M).

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.