János's profileJanka JánosPhotosBlogListsMore Tools Help

Janka János

Microsoft Fejlesztői Blog

János Janka

Occupation
Location
Me  
Photo 1 of 28
More albums (1)
Köszönjük a látogatást!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.
Manówrote:
Szia János!
 Itt jártam. Érdekes ez a tárhely.
Andi.
Apr. 29
Tibor Szűcswrote:
Szia János!
 
Az áprilisi blogbejegyzésed nagy segítséget nyújtott a munkámban. Köszi...
Dec. 11
Lists

HIM

 

Resurrection

There was a time
When I could breath
my life in you
One by one
Your pale fingers
started to move
And I touched your face
And all life was erased
You smiled like an angel
(falling from grace)...

 

Gone with the sin

I love your skin
oh so white
I love your touch
cold as ice
And I love every
single tear you cry
I just love the way
you're losing your life...


 

Salt in our wounds

Here we are
In the maelstrom of love
Waiting for the call
To sooth our hearts
Here we are
And don't know how to stop
Waiting for the war
To end it all...
July 04

Microsoft PressPass

Találok én mindig valamit: Microsoft PressPass. ;)

July 03

DE – Prog. info

Oh, látom még mindig jók az arányok. A mostani ki sincs téve. Biztos túl sokan végeztek, nem fért ki a lapra. Programtervező informatikust még nem is láttam kiírva, ahogy elnézem, pedig az BSc.-s szak, így 3 éves az alapképzés. Már lennie kellene valakinek azóta régen. Azt hittem valamelyik volt csoport-társam már végzett azóta. A főiskolai szakot (programozó matematikus) is már mikor megszűntették, de még látom, hogy vannak befutók. Biztos túl könnyű volt a kalkulus. ;) Ahogy látom a tematika se változott nagyon. Első 1/1.5 év tömény matek, majd csak utána jön a “lényeg”. Most nézem, hogy az informatikus könyvtáros szak meg tiszta elvont tárgyakkal van tele. Király, főleg hogy a kutyát nem érdekli most, hogy milyen diplomád van, csak szakirányú végzettség legyen. Ja, meg még ráadásul sokkal kevesebb kreditet is kell megszerezni hozzá kevesebb óraszámmal.

Na nem is ez itt a lényeg, hanem sokkal inkább az, hogy ki mit gondol arról, mennyire van köze az iskolai végzettségnek a üzleti életben való helytálláshoz, sikerességhez? Azért is teszem fel ezt a kérdést, mert engem is sokszor bonbáznak/bonbáztak ezügyben.

Ugye nem kell felsorolnom nagy neveket, akik felsőfokú iskolai végzettség nélkül is hatalmas dolgokat voltak képesek elérni pusztán önerőből/önszorgalomból. Sőt voltak itt már olyanok nyilvánosság előtt is - mint pl. Vágó István műsorában egy postás - akik megmutatták, hogy mennyire olvasottak és jól tájékozottak az élet dolgaiban. De én a közvetlen környezetemben is rengeteg ilyen sikeres vállalkozó embert ismerek, ugyanakor olyanokat is, akiknek van felsőfokú végzettségük, mégis rosszabbul élnek, mint ezek az emberek. Szóval a kérdés az, hogy mennyire függ össze ez a kettő szerintetek?

Írtam ide egy regényt előzőleg, de kiszedtem, nehogy befolyásoljam bárki válaszát is. :)

June 28

SQL Server adatbázisok szinkronizálása

Erre a témára kénytelen vagyok egy picivel több szót fecsérelni, mert ez az, ami az életben is talán a legtöbbször előfordul. Több megoldás is létezik egyébként a problémára. Egyik ezek közül a Microsoft SQL Server Integration Services használata. Ez utóbbival egyetlen probléma, hogy SQL Server Express felhasználók nem tudják használni. Másik megoldás a Sync Framework használata, én most ezzel fogok folglalkozni, mely nem csak kiadás-független, hanem SQL Server független is. Még mielőtt tovább mennénk, vegyünk egy pillantást a provider modellre, ami lényegesen változott az első CTP óta:

Microsoft Sync Framework v2.0 CTP2

Mint látható a képen, az újítások javarésze az adatbázisok szinkronizálását célozta meg. Született egy új, a relációs adatbázisok szinkronizálására alkalmas provider RelationalSyncProvider néven. Ennek leszármazottja egy DbSyncProvider, mely egy általános célú adatbázis szinkronizáló provider, illetve két specializált provider, melyek SQL Server / SQL CE adatbázisokhoz készültek (SqlSyncProvider, SqlCeSyncProvider). Mi most az utóbbi kettővel fogunk foglalkozni, de ez nem jelenti azt, hogy maga a feladat csupán ezekkel oldható meg. Ha saját változás-követési infrastruktúrát szeretnénk kialakítani, illetve magunk szeretnénk kezelni az alacsonyszintű metaadattárolási műveleteket, akkor még mindig lehetőség adódik használni ezek őseit. Néhány előnyét azért mégis felsorolnám a készhez kapott SqlSync providereknek:

  • Az SqlSyncProvider ugyan arra képes, mint a DbSyncProvider, de lényegesen kevesebb kód szükséges hozzá, illetve nem szükséges megérteni azokat a lekérdezeket, amiket a SyncFx használ, hogy szinkronizálja az adatokat. A DbSyncProvider megfelelő választás nem SQL Server adatbázisokhoz.

  • Az SqlSyncProvider és SqlCeSyncProvider használható kliens-szerver, peer-to-peer, illetve mixelt topológiákhoz, míg a DbServerSyncProvider és SqlCeClientSyncProvider csak kliens-szerver topológiákhoz alkalmas.

  • Az SqlSyncProvider és SqlCeSyncProvider flexibilis és könnyedén testreszabható. Lehetőséget adnak szinkronizálni minden kiadásával az SQL Servernek, beleértve az Express és CE 3.5 kiadásokat is.

Alapvető Sql Sync fogalmak

Szinkronizálása a csomópontoknak egy topológiában két fázisra bontható: a szinkronizációs csomópontok beállítása, illetve futtatása a szinkronizációnak egypár csomópont között. Az SqlSync providerek esetében a csomópontok beállítása két feladatból áll:

1. Definiálni, hogy mit szeretnénk szinkronizálni
Definiáljuk, hogy mit szeretnénk szinkronizálni leírva egy vagy több szinkronizációs területet (scope). Egy szinkronizációs terület nem más, mint táblák halmaza, melyek némelyike, esetleg mindegyike lehet egyénileg szűrt is. A táblák már létezhetnek az adatbázisban, vagy leírhatók Sync Framework objektumok által, aztán pedig generáltak futásidőben szinkronizálás közben.

Fontos megjegyezni: Amikor először szinkronizálunk létrejönnek a scope-ok és meta-adat táblák az adatbázisban. Bármilyen változtatást szeretnénk később végezni, pl. az elődleges kulcson változtatni valamelyik táblában, akkor a hozzá kapcsolódó meta-adat táblát és triggereket, tárolt eljárásokat is módosítanunk kell. B. megoldás, hogy dropolunk mindent és újra létrehozzuk az egész Sync infrastruktúrát. Épp ezért célszerű egy backup-ot csinálni az adatbázisból tesztelgetés előtt.

Amennyiben olyan táblát használunk, amiben az elsődleges kulcs egy auto inkrementált int érték, a SyncFx invalid SQL scriptet fog előállítani látszólag, de jogos, mivel nem képes beszúrni azt az ID-el rendelkező másik rekordot, mivel meghatározott auto inkrementált int értéket nem lehet beszúrni/frissíteni, ellenben GUID (newsequentialid())-et igen. Ilyenkor a megoldás vagy az, hogy más típúsú kulcsot (GUID) használunk, vagy mi magunk határozzuk meg a kulcsot a szinkronizációs sémában, eltávolítva az auto inkrementált kulcsokat (ez attól is függ, hogy mi alapján szeretnénk szinkronizálni):

private DbSyncTableDescription SetPrimaryKeys(DbSyncTableDescription tableSchema,
    params string[] keys)
{
    Contract.Requires(tableSchema != null);
    Contract.Requires(keys != null);
    Contract.Requires(Contract.ForAll(keys, key => key != null));

    for (var i = tableSchema.Columns.Count - 1; i >= 0; i--)
    {
        var column = tableSchema.Columns[i];

        if (column.IsPrimaryKey && column.AutoIncrementStepSpecified)
        {
            tableSchema.Columns.Remove(column);
        }
        else
        {
            column.IsPrimaryKey = keys.Contains(column.UnquotedName);
        }
    }
    
    return tableSchema;
}

2. Az adatbázisok ellátása változás-követési információkkal
Miután a táblák és területek leírása megtörtént, tudunk használni SyncFx objektumokat, hogy alkalmazzuk a generált scripteket minden csomópontra. A scriptek létrehoznak egy változás-követési és egy változás-alkalmazási infrastruktúrát ami meta-adat táblákból, triggerekből és tárolt eljárásokból áll. Minden táblához létrejön egy <táblanév>_tracking meta-adat tábla, melyek még az első szinkronizálás alkalmával feltöltődnek a hozzájuk tartozó tábla minden rekordjával, ellenben ezek a táblák csak a kulcsokat és a rekordokhoz tartozó change-tracking információkat tárolják.

Szinkronizációs területek (scopes)

Fontos megérteni, hogy egy scope kombinációja tábláknak és szűréseknek. Definiálhatunk olyan scope-ot is, ami csak a debreceni cégeket szinkronizálja, de mellé még egy olyat is, ami csak a pesti cégeket. Megjegyzendő, hogy a SyncFx nem kezeli azokat a törlési eseteket, amikor például egy debreceni cégből pesti lesz. Ilyenkor az egyik scope-ból átkerül a másikba az adat a szűrési kritériumok miatt, de a régi scope-ból nekünk kell azt törölni. Az alkalmazás feladata kezelni ezt a szituációt. Amennyiben nem szűrögetünk, hanem ökör módjára mindent szinkronizálunk (mint én is :)), ilyen gond nincs.

A scope-ok lehetnek eltérőek, vagy átfedhetik egymást. Két scope átfedi egymást, ha egymás között osztanak meg közös adatokat.

Van egy pár lehetőség scope-ok definiálására, de a következő elvet mindenesetben követni kell: bármilyen adat ami szinkronizált két adatbázis között a szinkronizációs topológiában csak egy scope-hoz tartozhat. Például az A és B adatbázis szinkronizálhat a Scope 1-el, az A és C adatbázis pedig a Scope 2-el egymás között. Az A és B nem tud szinkronizálni a Scope 2-el, mert az adott tábla sorok (pl. termékek) mindkét scope-hoz hozzátartoznak.

Hogyan is működik ez a gyakorlatban?

Először is, leírjuk a scope-ot és a táblákat. Én ezen általánosítottam egy picit, mint ahogy az SDK-ban van:

/// <summary>
/// Kinyeri a tábla sémát a meghatározott táblából.
/// </summary>
private DbSyncTableDescription GetTableSchema(string tableName)
{
    Contract.Requires(!string.IsNullOrEmpty(tableName));
    return SqlSyncDescriptionBuilder.GetDescriptionForTable(tableName, ServerConnection);
}

/// <summary>
/// Létrehoz egy szinkronizációs scope-ot.
/// </summary>
private DbSyncScopeDescription ScopeFromTableSchemas(
    params DbSyncTableDescription[] tableSchemas)
{
    Contract.Requires(tableSchemas != null);
    Contract.Requires(Contract.ForAll(tableSchemas, ts => ts != null));

    var scope = newDbSyncScopeDescription(ScopeName);
    foreach (var tableSchema in tableSchemas) scope.Tables.Add(tableSchema);
    return scope;
}

/// <summary>
/// Létrehozza a szinkronizációs infrastruktúrát.
/// </summary>
private void ApplyConfig(DbSyncScopeDescription scope)
{
    Contract.Requires(scope != null);

    var config = new SqlSyncScopeProvisioning(scope);
    config.SetCreateTableDefault(DbSyncCreationOption.Skip);
   
    // Konfigurálja a szerver adatbázist.
    if(!config.ScopeExists(ScopeName, ServerConnection))
        config.Apply(ServerConnection);

    // Konfigurálja a kliens adatbázist.
    if(!config.ScopeExists(ScopeName, ClientConnection))
        config.Apply(ClientConnection);
}

Fontos látni, hogy a konfigurálásnak ez csak az egyik módja. Jelen esetben a meghatározott scope-ot és sémát alkalmazzuk először a szerverre, azt követően pedig a kliensre, de lehetőség van már magából a szerverből kinyerni a szinkronizációs sémát (ha már ott létezik) és azt alkalmazni a kliens adatbázisra. Ez utóbbi módszer különösen hasznos, ha biztosítani szeretnénk, hogy konzisztens maradjon a szerver szinkronizációs séma a kliensével.

Használatára egy példa (a tábla sorrend számít):

ApplyConfig(ScopeFromTableSchemas
(
    GetTableSchema("Country"),
    GetTableSchema("StateProvince"),
    GetTableSchema("Settlement")
));

Ha ezzel idáig megvagyunk és megnézzük az adatbázist, akkor láthatjuk, hogy létrejöttek a change-tracking meta-adat táblák/triggerek/tárolt eljárások a szerveren és a kliensen egyaránt. Amennyiben valami gubanc van a létrehozás közben, megtehetjük azt is, hogy az infrastruktúrát létrehozó SQL scriptet még az alkalmazási folyamat előtt kimentjük például egy fájlba és mi magunk kézzel kezdjük el javítgatgni, majd futtatni azt az SQL Management Studio segítségével:

File.WriteAllText("Sync.sql", config.Script(‘DatabaseName’));

Ezt még a config.Apply() előtt meg kell tenni, ha esetleg valami probléma adódna a script futtatásakor. Utolsó lépésben pedig semmi más dolgunk nincs, mint egy SyncOrchestrator-t felhasználva szinkronizálni a két csomópont között:

public void Synchronize()
{
    Init();

    var clientProvider = new SqlSyncProvider(ScopeName, ClientConnection);
    var serverProvider = new SqlSyncProvider(ScopeName, ServerConnection);

    Orchestrator.Direction = SyncDirectionOrder.UploadAndDownload;
    Orchestrator.LocalProvider = clientProvider;
    Orchestrator.RemoteProvider = serverProvider;

    clientProvider.SyncProgress += ...;
    serverProvider.SyncProgress += ...;
    clientProvider.ApplyChangeFailed += ...;

    DisplayStat(Orchestrator.Synchronize());
}

A kliens és a szerver szinkronizáció is szépen végigkövethető a SyncProgress események segítségével, de ezen felül kapunk egy végső összegzést is, amivel a Synchronize() szinkron metódus-hívás tér vissza. Az ApplyChangeFailed eseményre feliratkozva kezelhetjük a szinkronizálás közben bekövetkező konfliktusokat is a következő Action értékekből választva:

private void clientProvider_ApplyChangeFailed(object sender,
DbApplyChangeFailedEventArgs e) { e.Action = ApplyAction.Continue; e.Action = ApplyAction.RetryApplyingRow; e.Action = ApplyAction.RetryNextSync; e.Action = ApplyAction.RetryWithForceWrite; ShowException(e.Error);
}

Mindebből egész jól látszik, hogy mennyire egyszerű szinkronizálni adatbázisok között. Ha van érdeklődés a téma iránt, akkor folytatom ezt a cikksorozatot a későbbiekben is, mert ez még hiányos egy picit, de ízelítőnek egyenlőre ennyi is elég.

June 16

Balatonlelle (július 3.-4.-5.)

Szeretném tudatni mindenkivel (hátha arra kószál valaki a blogomról), hogy a lellei juliális ezeken a napokon lesz és én is ott leszek. A főzőversenyen halászlét fogunk csinálni, mint minden évben, úgyhogy ha gondoljátok, ugorjatok le egy hétvégére és egy díjnyertes ebédre. Mellesleg ilyenkor mindig jó a felhozatal is. :)

June 11

WPF 4.0 Data Binding Pipeline

A WPF 4.0 adatkötési pipeline-ba is történt némi változás, ami képes a tulajdonságok újraolvasására a kötési forrásból miután a control beállítja a tulajdonság értékét.

Mit jelent ez?

Azt, hogy ha valaki módosít egy controlba egy értéket, de az alatta lévő üzleti objektum kötött tulajdonságának settere tovább módosítja azt, akkor az is automatikusan visszaíródik a control-ba.

Például:

  1. A felhasználó beüti azt az email címet, hogy: “x @msn.com”. Direkt raktam egy szóközt az x után.

  2. Az üzleti objektum email cím tulajdonsága megváltozik.

  3. Az üzleti szabályok módosítják a tulajdonság értékét. Teszem fel korrigálják az email címet eltávolítva a felesleges szóközöket. Utána kiváltódik az OnPropertyChanged esemény a property setterbe.

  4. Az adatkötési motor most automatikusan újraolvassa a tulajdonságot és beállítja a TextBox.Text tulajdonságát a helyes (“x@msn.com”) email cím értékkel, ami persze az üzleti objektum email cím tulajdonságának aktuális értéke is egyben.

Tehát mondhatjuk, hogy van egy új feedback mehanizmus, amit a WPF adatkötési motorja képes kezelni mostantól kezdve.

XAML 2009 (.NET FX 4 Beta 1)

Engedelmetekkel larántám a leplet néhány XAML 2009 újdonságról, mert kemény fejlesztéseken megy keresztül a WPF 4.0 is. Arról rengeteg hír van már most, hogy a VS Cider WPF designer designtime támogatása mennyit javult a VS2010-re - ténylegesen stabilabb és “egészségesebb” designer mint az előző verzió volt - de arról kevesen ejtenek szót, hogy a XAML nyelv merre fejlődik tovább. Megjegyzendő, hogy az alábbi XAML 2009 featurök még csak nyomokban működnek a VS 2010 Beta 1-ben. Például át kell állítani az adott XAML markup fájl Build Action tulajdonságát Page-ről Resource-ra, mely elkerüli, hogy a XAML markup leforduljon BAML/IL-re. Ez szükséges a Beta 1-ben, mivel az új nyelvi szintaxis még nem mindenhol támogatott. Illetve szükséges felvenni a System.Xaml assembly-t is a referenciák közé.

Én ezeket az új featuröket úgy teszteltem, hogy készítettem egy ResourceDictionary-t, abba raktam DataTemplate-eket, majd az ablakba egy ContentPresenter segítségével megjelenítettem a tartalmat.

Még mielőtt nyelvi dolgokról írnék, némi összefoglalóval szolgálnék már így az elején. Először is a XAML kezeléssel kapcsolatos létező összes kód folyamatosan kerül ki a System.Xaml.dll assembly-be a WindowsBase.dll-ből. Tehát maga a XAML nyelv teljesen WPF független lesz. Létezik egy új következetes XAML stack, ami megosztott mind WPF/WCF/WF között egyaránt. További jó hír, hogy egyetlen parser lesz x db helyett (sablonokhoz, stílusokhoz, stb.), ami garantálja a konzisztens viselkedést mindenhol. Ezenfelül a WPF team készít egy API-t BAML olvasáshoz, mely többek közt lehetőséget ad analizálni a XAML/BAML-t ugyanazzal az API-val…

De ami igazán érdekel bennünket, hogy milyen nyelvi újdonságok várnak ránk:

1. Name References

Az {x:Reference …} egy jópofa kis egyszerűsítés, amely lehetőséget ad arra, hogy explicit beállítsuk az értékét a céltulajdonságnak egy referenciával az elnevezett objektumra. Hasonlít a {Binding ElementName=…} típusú kötésekhez, ellenben ez parser szintű, így csak az adott NameScope-on belül képes megtalálni valamit. Így működik:

<DataTemplate x:Key="CT">
  <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
      <TextBox Name="myTextBox" Width="200"/>
      <Label Target="{x:Reference myTextBox}" Content="Text"/>
  </StackPanel>
</
DataTemplate>

Így már nem működik:

<DataTemplate x:Key="CT">
    <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
        <Label Target="{x:Reference myTextBox}" Content="Text"/>
        <TextBox Name="myTextBox" Width="200"/>            
    </StackPanel>
</DataTemplate>

Valójában mindössze egy NameReferenceConverter nevű típuskonverter van a háttérben. Azt, hogy valójában mi egy típuskonverter, már korábban leírtam: itt. Illetve mégegy apróság, ellenben a Binding-al, ez nem támogatja az adatkontextus kötéseket {x:Reference} != {Binding}.

2. Generikus típusok támogatása XAML-ben

<DataTemplate x:Key="Template">
    <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
        <StackPanel.DataContext>
            <om:ObservableCollection x:TypeArguments="x:String">
                <x:String>A</x:String>
                <x:String>B</x:String>
                <x:String>C</x:String>
            </om:ObservableCollection>
        </StackPanel.DataContext>
        <ListBox ItemsSource="{Binding}"/>
    </StackPanel>
</DataTemplate>

Generikus típus-paraméterek az új x:TypeArguments segítségével adhatók meg. Ez a példa egyben azt is demonstrálja, hogy mostantól az alap típusok az {xmlns:x:..} XAML névtéren belül úgyszint elérhetők, mint pl. a string típus is: <x:String/>. Nincs szükség 76 névteret felvenni.

Több típus-argumentumot egymással vesszővel elválasztva adunk meg:

<gcc:Dictionary x:TypeArguments="x:String, x:Double"/>

Mi van akkor, ha egy generikus típus-paraméter is egy generikus típus? Semmi, ugyanis lehetőség van generikus típusok beágyazására is a következő szintaxissal:

<gcc:Dictionary x:TypeArguments="x:String, gcc:List(x:Double)"/>

3. Egyszerűsített elérése az alap típusoknak

Néhány példa:

  • <x:Boolean>True</x:Boolean>
  • <x:String>Text</x:String>
  • <x:Double>1</x:Double>

4. Paraméter nélküli (nem default) konstruktorok és factory metódusok támogatása

x:Arguments
x:FactoryMethod

5. Események XAML által

XAML: Events in compiled and uncompiled scenarios

Egyenlőre még nem nagyon merülök bele a további részletekbe, mert még a VS 2010 Beta 1 is nagyon gyengén muzsikál ezekkel a dolgokkal. Az viszont a napnál is világosabb, hogy a legnagyobb cél a közös XAML stack megalkotása, így pl. a WCF is ezt fogja használni. Ezzel megy el a legtöbb idő a csapaton belül (a nyilatkozatok alapján), mivel jóval bonyolultabb ezt kivitelezni, mint elsőre gondolták.

June 10

SQL szerverből SQL szerverbe

Az SqlSyncProvider használatával kapcsolatban készítettem egy nagyon alap kis demonstrációs bemutatót. Nem csalás, nem ámítás, ténylegesen ennyi kód megvalósítani az SQL Server adatbázisok közötti szinkronizációt:

using System;
using System.Data.SqlClient;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServer;

namespace VVMF.Samples.Sync
{
    class Program
    {
        static void Main(string[] args)
        {
            // Helyi és távoli kapcsolatok létrehozása (SqlConnection).
            var localConnection = new SqlConnection(
                @"Data Source=local;Initial Catalog=DB1;Integrated Security=True");
            var remoteConnection = new SqlConnection(
                @"Data Source=local;Initial Catalog=DB2;Integrated Security=True");
            
            // Létrehoz egy szinkronizációs scope-ot,
            // amin belül minden látszik a SyncFx számára.
            var scopeDesc = new DbSyncScopeDescription("Filtered_Organization");
            // Hozzáadjuk a scope-hoz az Organization táblát.
            // A GetDescriptionForTable() metódus kinyeri a tábla sémát.
            scopeDesc.Tables.Add(
                SqlSyncDescriptionBuilder.GetDescriptionForTable(
                "Organization", remoteConnection));            

            // Konfiguráljuk a szinkronizációs scope-ot.
            // Nem kell létrehozni a táblákat, mert már léteznek.
            var remoteConfig = new SqlSyncScopeProvisioning(scopeDesc);            
            remoteConfig.SetCreateTableDefault(DbSyncCreationOption.Skip);

            // Jelen esetben csak az W betűvel keződőek lesznek szinkronizálva.
            remoteConfig["Organization"].AddFilterColumn("Name");
            remoteConfig["Organization"].FilterClause = "[side].[Name] LIKE 'W%'";
            
            // Konfiguráljuk a scope és változás-követési infrastruktúrát,
            // feltéve, hogy még nem létezik. Ez fogja létrehozni a táblanév_tracking
            // táblákat, illetve triggereket, SP-ket, stb.
            if (!remoteConfig.ScopeExists("Filtered_Organization", remoteConnection))
                remoteConfig.Apply(remoteConnection);
                        
            // A távoli adatbázison már elkészítettük a change tracking infrastruktúrát.
            // Itt csak kinyerjük a jelenlegi állapotot a szerverről és alkalmazzuk
            // a kliens adatbázisra is.
            var localSqlDesc = SqlSyncDescriptionBuilder
                .GetDescriptionForScope("Filtered_Organization", remoteConnection);
            var localConfig = new SqlSyncScopeProvisioning(localSqlDesc);

            if (!localConfig.ScopeExists("Filtered_Organization", localConnection))
                localConfig.Apply(localConnection);

            // Helyi és távoli szinkronizációs providerek létrehozása (SqlSyncProvider).
            var orchestrator = new SyncOrchestrator();
            orchestrator.LocalProvider =
                new SqlSyncProvider("Filtered_Organization", localConnection);
            orchestrator.RemoteProvider =
                new SqlSyncProvider("Filtered_Organization", remoteConnection);

            // Egyéni adat-transzformációs konverterek hozzáadása.
            // Jelen esetben szükségtelen, mert mindkét oldalon ugyanolyan
            // szerkezetben van ábrázolva a cég.
            //   orchestrator.LocalDataConverter = ...
            //   orchestrator.RemoteDataConverter = ...

            // Néhány eseménykezelő, hogy tudjuk hol tart a folyamat.
            orchestrator.StateChanged += (sender, e) =>
                Console.WriteLine(
string.Format("{0} => {1}",
e.OldState.ToString(), e.NewState.ToString())); orchestrator.SessionProgress += (sender, e) => Console.WriteLine(
string.Format("{0}/{1}",
e.CompletedWork, e.TotalWork)); orchestrator.Direction = SyncDirectionOrder.UploadAndDownload; orchestrator.Synchronize(); // Bontjuk a kapcsolatokat. remoteConnection.Close(); remoteConnection.Dispose(); localConnection.Close(); localConnection.Dispose(); Console.Write("\nPress any key to exit."); Console.Read(); } } }

A SyncFx minden mást megcsinál helyettünk. Íme az eredmény:

image

Kb. 3 sorral több elintézni azt, hogy ez egy harmadik, teszem fel SQL Server Compact adatbázisba is leszinkronizálódjon. Természetesen amit fentebb mutattam csak egy alap példa, jóval testreszabhatóbb ennél a dolog. Például még a szinkronizáció során fenntartott cache memória mérete is szabályozható.

Microsoft Sync Framework 2.0 CTP2

A Sync csapat kiadta a következő CTP-t. Végre van szinkronizáció provider direkt SQL Server adatbázisokhoz is! Az újdonságokról röviden:

SqlSyncProvider / SqlCeSyncProvider

Az SqlSync providerek lehetőséget adnak hub-and-spoke és peer-to-peer szinkronizáció megvalósítására Microsoft SQL Server, Microsoft SQL Server Express, illetve Microsoft SQL Server Compact adatbázisok esetében. A Sync Framework automatikusan létrehoz minden parancsot ami szükséges a kommunikáció megvalósításához minden adatbázissal. Nincs szükség se szinkronizációs lekérdezések írására, mint más bázis (pl. DbSyncProvider) providerek használata esetén, és nincs szükség a meta-adat tároló karbantartására sem. Ezek a providerek támogatják az inicializációs beállítások rugalmas testreszabását, adatméret alapú kötegelt változásokat, és mindezt teszik úgy, hogy közben a meta-adatok tisztítása és a szerver adatbázis helyreállítása is az ő feladatuk.

Custom filters

Az egyéni szűrők lehetőséget adnak egy szűrt replikának hogy tároljon tételadatokat csak azon tételekhez, amelyek a szűrésben szerepelnek, mint pl. egy média tároló replika, mely csak tárolja azokat a dalokat, melyek az értékelés során legalább 3 csillagot kaptak. Amint megváltozik egy dal értékelése, egy tétel bekerülhet/kikerülhet a szűrésből. Egy szűrés-követő replika egy másik replika a közösségben, amely tudja azonosítani, hogy mely tételek szerepelnek a szűrésben és melyek voltak betéve/kivéve mostanában. Egy szűrés-követő replika tárolhat tétel adatokat azon tételekhez is, melyek nem szerepelnek a szűrésben. A Sync Framework támogatja a hatékony szinkronizációt a szűrt replikák és a szűrés-követő replikák között és gondoskodik a meta-adatok kezeléséről.

Improved conflict handling

A CTP2 hozzáad jónéhány új funkciót a SyncFx-hez, ami egyszerűsíti a szinkronizáció során előfordulható konfliktusok kezelését. További részletek erről itt.

Data conversion between providers

Na ez már egy érdekesebb dolog, főként, hogy nekem is pont ez kell. Néhány esetben a szinkronizációs providerek szinkronizálnak ugyanolyan típusú adatokat (jelen esetben nálam pl. céges adatokat), de az adat formátuma ami a provdereknek szükséges különböző. Jelen esetben nálam ez egy bazi nagy denormalizált excel tábla az egyik végen, a másik végen pedig egy EF model alapú adatbázis. A Sync Framework úgy tűnik, hogy mostantól lehetőséget ad arra, hogy implementáljunk interfaceket, melyek konvertálják az adatokat a providereknek megfelelő formátumba. Ez az adatkonverziós API lehet használt bármilyen típusú egyéni szinkronizációs providerrel, továbbá a Sync Framework tartalmaz konverziós API-t kimondottan a fájl szinkronizációs providerhez is.

Change application service

A Sync Framework (a korábbi CTP is) tartalmaz egy change applier implementációt, amit a legtöbb alkalmazás használ alkalmazni a változásokat egy replikára. A CTP2 viszont bevezeti a change application service-t, mely ugyanazokat a műveleteket teljesíti, mint a change applier, de sokkal testreszabhatóbb módon. Ha egy destination providernek szükséges nagyobb rugalmasság, mint az alap változás alkalmazó, lehetősége van használni a change application service-t, hogy teljesítse csak egy halmazát a szükséges műveleteknek.

A telepítéssel kapcsolatban annyit még, hogy le kell szedni az előző CTP-ket előtte. A telepítő pedig innen letölthető: Microsoft Sync Framework 2.0 CTP2. Hajrá!

June 01

Microsoft Bing

Elindult a Microsoft új keresője, a Bing. Egyenlőre még az összes feature használatához szükséges US-re állítani az ország/régió beállításokat, illetve angolra a nyelvet, de ígéretesnek néz ki nagyon. Néhány hasznos dolog, amit kigyűjtöttem erről (a képeken pirossal részletezve vannak a figyelemre méltó újdonságok): itt.

www.Bing.com /www.discoverbing.com

May 27

WPF - Model-View-ViewModel bevezető

A Microsoft azért adta ki az MVVM Toolkit-et, hogy egy egyszerűbb és jobban áttekinthető megoldást nyújtson minden WPF fejlesztő számára. Ez annyira igaz, hogy elvileg része lesz az eljövendő RTM verziónak, illetve a WPF 4.0 parancsrendszerét is megreformálják pont emiatt. Voltak páran akik megkértek mostanában, hogy meséljek erről az MVVM-ről néhány szót. Mivel ez a téma elég szerteágazó és kisezer megközelítés létezik, hozzávetőlegesen megpróbálom bemutatni a Microsoft-os WPF MVVM elképzelést, mely pont az egyszerűséget és könnyen érthetőséget hivatott megcélozni.

Először is a lényege az MVVM-nek, akárcsak az ősének az MVC-nek, hogy szeparálja a kód logikát a felhasználói felülettől. Egy jól tervezett alkalmazás ismérve a könnyű fejleszthetőség, tesztelhetőség, illetve fenntarthatóság.

Model

A model definiálja az adatot, ami használt az alkalmazásban. Ezek általában implementálják az INotifyPropertyChanged interfészt. Fontos, hogy ez a réteg ha lehet ne tartalmazzon WPF specifikus kódot (mint pl. ObservableCollection), hogy újrahasználható legyen más típusú projektekben is. A model szerepe többek között az alatta lévő adatforrás logikailag egybefüggő részeinek ábrázolása/egyszerűsítése. Miért fontos ez az utóbbi mondat? Azért, mert nem keverendő össze az adathozzáférési réteggel. Illetve ez is olyan, hogy mikor igen, mikor nem, teljesen megvalósítás függő, de általában a tiszta megoldás az, amikor a model becsomagolja ezeket az adatforrás osztályokat, tehát pl. egy Entity Framework model esetében lehetőség van a Country, StateProvince, Settlement, Address entitásokat egyként, például LocalityData ábrázolni a modelben és a szükséges tulajdonságokat kitenni. Ugyanígy nemcsak a tényleges adat kerül itt ábrázolásra, hanem az alapvető funkciók is, mint pl. egy MessengerData osztály Connect(), Disconnect() metódusa is. A model osztályok sose kommunikálnak a ViewModellel, illetve a View-al, mindent események formájában tesznek ki, amikre a ViewModel objektumok feliratkozhatnak.

View

A View definiálja a felhasználói felületet. Amikor lehet ez legyen írva mindig tisztán XAML markupban. A WPF ugyan támogatja az eseményeket a code-behind fájlokban (akárcsak a WinForms és VCL), de az állapotadatok (mint pl. a kiválasztott tételek, aktuális tétel, vagy akár direkt szabályozható WindowState, stb.) és üzleti logika semmikép ne legyen ide írva. Ezeket a ViewModel-ben kell tartani.

ViewModel

A ViewModel absztraktálja amit a View reprezentál. A ViewModel tárolja a vizuális állapotokat (mint pl. a kiválasztott felhasználókat: ObservableCollection<User> SelectedUsers) és kitesz action-öket, amik végrejtottak a UI-ban WPF parancsok által. A View rögzít egy referenciát a ViewModel-re és használja a meghatározott ViewModel-t a WPF adatkötési lehetőségei által. Ez utóbbi általában a WPF FrameworkElement származékok DataContext tulajdonságán keresztül valósul meg. A FrameworkElement pedig közös őse minden WPF vezérlőnek.

A sorrend nagyon fontos !

MVVM A View tudatában van a ViewModel-nek, de a ViewModel nem tud semmit a View-ról. Ugyanígy a ViewModel tudatában van a Model-nek, de a model semmit sem tud a ViewModel-ről.

Megjegyzendő:

Vannak példák, amikor csak három réteget használnak, pl. az Entity Frameworkot mint Model-t, s nem mint DAL-t (Data Access Layer) használják. Az én véleményem erről az, hogy szükséges a plusz model réteg, mert ott logikai ábrázolás, illetve validációs logika implementálás is történik. Ez utóbbira is vannak ellenpéldák, amikor a ViewModel-be implementálják a validációt, de látni fogjuk, hogy sokminden javarészt feladattól függ.

 

Szemléltetésképp készítettem egy kis termékmenedszer példát (még VS 2008-ban), amin keresztül lépésről lépésre megtudjuk nézni ezt a gondolatmenetet. A kód innen letölthető: MVVM.zip

Mint ahogy a ábrán is látszik az egyes részek elhatárolódása, ugyanúgy szépen látszik ez a kódon is.

Vew > ViewModel > Model > DAL

View < ViewModel < Model < DAL

image

Haladjunk sorjában:

Először is készítünk egy adatbázist. Ez egy egyszerű kis adatbázis, mindössze 1 táblát tartalmaz (Products) néhány mezővel, mint Name, Description, Price. Néhány tesztadatot érdemes felvenni bele.

DAL

Most jön az adatelérési réteg (Data Access Layer). Ide kerül az Entity Framework kontextus és minden alapvető adatelérési funkció, illetve a compiled query-k is. Jelen esetben a ProductProvider osztály lesz az, ami szolgáltatja nekünk az adatokat. Ennek az osztálynak van egy LoadProducts(), AddProduct(), DeleteProduct(), SaveProducts() metódusa. Tulajdonképpen a lényeg az, hogy egy kontextus van a termékek manageléséhez. Ezt lehet természetesen generikusabbá tenni, de így sokkal könnyebb megérteni elsőre azok számára is, akik most először találkoznak a témával.

Model

Jön a model réteg. Itt készítünk egy ProductData osztályt, ami a meglévő EF Product entitás osztályunkat fogja becsomagolni, illetve kibővíti azt más tulajdonságokkal. Ebbe a rétegbe visszük le a validációs logikát is. Minden adatot reprezentáló model osztály implementálja az INotifyPropertyChanged, illetve IDataErrorInfo interfészeket. Jelen esetben én ezt leegyszerűsítettem egy ObservableObject és ValidableObject segítségével.

Ugyanitt kerül implementálásra a ProductBook osztály. Ez reprezentál egy terméklista manager osztályt. Ő már maga tárolja a becsomagolt termék objektumokat, de semmilyen állapotadatot nem tárol, mint pl. ki van kijelölve. Direkt NotificationCollection-be vannak téve a termék adatok, hogy ne függjön a kód a WPF-től. Ebbe az osztályba kerülnek megvalósításra az alapvető műveletek is, mint pl. új termékadat felvétele, egy termékadat törlése, illetve a módosítások mentése.

ViewModel

Itt alapvetően két darab ViewModel osztály létezik, az egyik a MainViewModel, a másik pedig a ProductBookViewModel. Készíthetnénk külön ViewModel osztályt magának a ProductData-nak is, de jelen esetben ez szükségtelen. Az annyit tenne mindössze, hogy magát a ProductData-t is becsomagolnánk egy ProductDataViewModel osztályba és ezt használnánk a ProductBookViewModel-ben, de ez nem szükséges, mivel a validáció a modelben van és nincs szükség bővíteni vizuális állapotinformációkkal ezt. Mint láthatjuk a kódban, a ProductBookViewModel konstruktora példányosítja a model (ProductBook) manager osztályt és feliratkozik annak a kollekciójának CollectionChanged eseményére, hogy diszpécselje a változásokat a model listájából a ViewModel ObservableCollection-be. Tehát magyarul szinkronban tartjuk a modellünk kollekcióját a view modellünk kollekciójával, ami már egy WPF specifikus kollekció (ObservableCollection), ugyanis ezt fogjuk bekötni a view objektumaink lista vezérlőibe, pontosabban ezeknek egy nézetét (CollectionViewSource).

Tehát most ott tartunk, hogy a ViewModel-ünknek van egy példánya a hozzá tartozó modelből: röviden a ProductBookViewModel objektumunk ismeri a ProductBook model objektumot.

Következő lépésben minden commandot kipakolunk ide. Azt, hogy épp ki van kijelölve a SelectedProductData tulajdonság határozza meg a ViewModel-be. Tehát a törlés parancsunk például így néz ki:

private void DeleteProductData()
{           
    ProductBook.DeleteProductData((ProductData)SelectedProductData);           

View

Ha megnézzük a ProductBookView.xaml fájlt máris megértünk mindent. Látható, hogy a lista control be van kötve a view model osztályunk megfelelő tulajdonságaiba. A kötésben az adatforrás ilyenkor a legközelebbi ráeső DataContext, mely jelen esetben a UserControl-unk DataContext-je lesz, ami ugye nem más lesz, mint egy ViewModel objektum.

<ListBox ItemsSource="{Binding ProductDataCollectionView, Mode=OneTime}"                
              SelectedItem="{Binding SelectedProductData, Mode=TwoWay}"
              ItemTemplate="{StaticResource ProductDataTemplate}"
              SelectionMode="Single" IsSynchronizedWithCurrentItem="True"/>

Mivel a SelectedItem = ViewModel.SelectedProductData és TwoWay módú a kötés, automatikusan szinkronba fog maradni a két tulajdonság a view és viewmodel között. Igen ám, de honnan a frászból tudja a view, jelen esetben a user controlunk, hogy melyik ViewModel objektumba kell kötni? Azaz honnan tudja, hogy melyik ViewModel az adatforrás, a DataContext, stb.? Na ezt szabályozza a konverterünk (LogicTypeInstanceConverter). Fontos, a View-okat nem használhatjuk a ViewModel osztályokba! Emlékezzünk, a sorrend fontos! Csináltam pontosan ennek illusztrálására mégegy View-ot (WelcomePageView.xaml) és a konverterünk automatikusan egy enum értékből (public enum LogicType { WelcomePage, Products }) fogja eldönteni, hogy milyen View/ViewModel logikai párost kell visszaadni, amit a MainView.xaml egy ContentPresenter kontrolja fog megjeleníteni:

<ContentPresenter
     Margin="5" Content="{Binding LogicType,
           Converter={converters:LogicTypeInstanceConverter}}"/>

Tehát én egy LogicType enum értékhez kötök, a konverterem pedig a cache-ből előkotorja nekem a megfelelő View + ViewModel párost, illetve ha nem léteznek, akkor automatikusan példányosítani is fogja őket. Ez itt a lényeg, ez párosítja össze őket, de bárhogy kombinálhatnánk ezeket további View és ViewModellekkel, ha lennének:

_logics = new Dictionary<LogicType, LogicTypePair>();

// View = WelcomePage, ViewModel = NINCS                              
_logics.Add(LogicType.WelcomePage, new LogicTypePair(typeof(WelcomePage), null));

// View = ProductBookView, ViewModel = ProductBookViewModel
_logics.Add(LogicType.Products, new LogicTypePair(typeof(ProductBookView), typeof(ProductBookViewModel)));

Sajnos elég nehéz ezt az egész metodikát szavakba önteni, vázlatosan lehet, de szájbarágósan elég nehéz ezt elmagyarázni, úgyhogy ha bárkinek aki még nem nagyon foglalkozott a témával bármi kérdése van, tegye fel nyugodtan. Mindenesetre próbáltam a kódot elég részletesen kommentezni, hogy világos legyen mi miből jön. Talán úgy a legegyszerűbb megérteni, ha az aljából indulunk ki: DAL <= Model <= ViewModel <= View és egyszerre csak egy rétegre összpontosítunk. Így viszonylag egyszerű lesz végigkövetni az egész folyamatot. Sok sikert!

May 19

WPF konverterek

Készítettem egy gyors módszert a konverterek használatához. Először is, ugye ahhoz, hogy konvertereket tudjunk használni mindig fel kell vennünk őket az erőforrások közé, majd StaticResource segítségével bekötni őket. Ezzel csak az a probléma, hogy egy idő után fárasztóvá válik a dolog. Tehát, az első akadályozó tényezőre a megoldás XAML markup extension-ök használata. Csinálunk először is egy generikus bázis osztályt, ami az összes konverterünk őse lesz markup támogatással:

using System;
using System.Globalization;
using System.Windows.Markup;
using System.Windows.Data;

namespace VVMF.Core.WPF.Converters
{
    [MarkupExtensionReturnType(typeof(IValueConverter))]
    public abstract class ConverterMarkupExtension<T> : MarkupExtension, IValueConverter
        where T: class, IValueConverter, new()
    {
        private static T _converter = null;

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (_converter == null)
                _converter = new T();

            return _converter;
        }

        #region IValueConverter Members

        public abstract object Convert(object value, Type targetType,
            object parameter, CultureInfo culture);

        public abstract object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture);

        #endregion
    }
}

Ezt követően azért, hogy az értéktípusokat/nullázható típusokat is könnyedén tudjuk kezelni, készítettem egy egyszerű kis segédosztályt:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VVMF.Core.WPF.Converters
{
    public static class ConverterHelper        
    {
        public static TValueType GetValue<TValueType>(
            object obj, TValueType defaultValue) where TValueType : struct
        {
            TValueType value = defaultValue; 

            if (obj is TValueType)
            {
                value = (TValueType)obj;
            }
            else if (obj is TValueType?)
            {
                var nullable = (TValueType?)obj;
                value = nullable ?? defaultValue;
            }

            return value;
        }

        public static TValueType GetValue<TValueType>(
            object obj) where TValueType : struct
        {
            return GetValue<TValueType>(obj, default(TValueType));
        }
    }
}

Majd tesztnek készítünk egy egyszerű kis szám inkrementáló konvertert:

using System;
using System.Windows.Data;
using System.Globalization;

namespace VVMF.Core.WPF.Converters
{
    [ValueConversion(typeof(int), typeof(int))]
    public class IntegerIncrementerConverter :
        ConverterMarkupExtension<IntegerIncrementerConverter>
    {
        public override object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {            
            return ConverterHelper.GetValue<int>(value) +
              ConverterHelper.GetValue<int>(parameter, 1);
        }

        public override object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            return ConverterHelper.GetValue<int>(value) -
              ConverterHelper.GetValue<int>(parameter, 1);
        }
    }
}

Ezután már XAML-be mindössze ennyit kell írnunk:

<TextBlock Grid.Column="0" Text="{Binding SelectedIndex,
  Converter={coreconverters:IntegerIncrementerConverter}, 
ElementName=dgOrganizations}" Margin="5"/>

Ehelyett:

<UserControl.Resources>
    <coreconverters:IntegerIncrementerConverter
x:Key="IntegerIncrementerConverter"/> </UserControl.Resources>
<TextBlock Grid.Column="0" Text="{Binding SelectedIndex,
                Converter={StaticResource IntegerIncrementerConverter},
                ElementName=dgOrganizations}" Margin="5"/>
May 12

Delphi Prism májusi release

A májusi kiadás már megpróbál bevezetni egy-két újdonságot még a .NET 4.0 és C# 4.0 előtt. Az új feature-k részletes leírása itt megtalálható. Ezen a listán szerepel a volatile fieldek, a generic type variance–ok, illetve újabb LINQ operátorok támogatása is, mint pl. skip, while, take és take while.

May 08

WPF IntelliSense: hol volt, hol nem volt

Egyes CTP-k telepítése után érhet bennünket meglepetés, mint pl. eltűnik a XAML IntelliSense a VS-ből. Mondanom sem kell, hogy nem éppen kellemes meglepetés volt a dolog. Mondom a megoldást máris:

  1. El kell indítani a cmd.exe-t admin joggal
  2. Újra be kell regisztrálni a következő COM könyvtárat:
    regsvr32
    "C:\Program Files (x86)\Common Files\microsoft shared\MSEnv\TextMgrP.dll"
May 07

Windows® API Code Pack for Microsoft® .NET Framework (v0.85)

Érkeznek az utángyártott managelt könyvtárak a Windows 7 újdonságainak kihasználáshoz is többek között. Na meg persze a Vista olyan szolgáltatásaihoz is, melyekhez nem voltak managelt könyvtárak (lsd. DWM). Úgy látom, hogy mostanában ez a divat, akárcsak a Sync Framework esetében (minden megy natív kódba és arra húznak még egy managelt réteget). Letöltés itt.

May 04

WPF Futures

WPF-hez megjelent az első Model-View-ViewModel Toolkit 0.1. Letöltés itt.