Mim

Select * from Seb where Sujets in (SQL Server 2008, ADO.NET, Visual Studio 2008)

août 2008 - Messages

DataSet, N Tiers, Linq To Sql

Quel rapport entre DataSet, une architecture multi-tiers et Linq to SQL ?

Pas facile ? Si si, juste un Designer qui génère tout pour nous dans Visual Studio 2008, que ce soit pour un DataSet typé ou pour un DataContext Linq To SQL.

Oui mais voilà, là où ça se complique (si on peut dire) c' est lorsque qu' on veut faire une petite architecture 3 tiers (soyons pas fou, restons simple)

 

Dans Visual Studio 2008, le Designer du DataSet est capable de se générer dans 2 projets distincts.

  1. Un projet contenant les DataTables typés.
  2. Un projet contenant les TableAdapters typés.

Tout ça grâce à la petite option magique : DataSet Project

image

 

Voilà à  quoi ressemble ma solution :

image 

C' est beau, c' est propre et ça marche.

 

Alors, je vous le demande, Pourquoi ils n'ont pas fait pareil pour Linq To SQL !!!

J' avais espéré secrètement que le SP1 de Visual Studio 2008 apporte cette feature, mais ce n'est pas le cas (si je me trompe, montrez moi, je serai super heureux si c'était le cas)

Alors comment on fait aujourd'hui ? eh ben à la main, on passe le DataContext en internal et on copie colle le DataContext dans l'assembly service en le repassant en public.

Et si on rajoute des tables au schéma, bah on recommence l'opération.

C'est pas critique, mais je trouve ça dommage ...

 

Voilà mon coup de gueule du jour :)

Visual Studio 2008 SP1 Release. Framework 3.5 SP1 Release

Et voilà, aprés la sortie en release de Sql Server 2008, voici venir aujourd'hui le service pack 1 de Visual Studio 2008 (et par la même occase le sp1 du fx 3.5)

http://msdn.microsoft.com/en-us/vstudio/cc533448.aspx

Et hop !

Sql Server 2008 Alive !

image

Et voilà, SQL Server 2008 est enfin sortie en Release 1.0

Allez Hop, un ti tour sur MSDN et voilà la bonne surprise !

image

Allez, va falloir faire péter la bande passante ! Big Hug

Posted: août 07 2008, 10:51 par Mimetis | avec no comments
Classé sous :
Sql Server CE, Multi Threads, Multi User. Part II

Ce post fait suite au post d'hier, au sujet du multi threading avec Sql Server CE

Dans l'exemple, nous avons démontrer que nous ne pouvions pas faire de multi threading et que nous remontions de vieilles exceptions pas extraordinaire.

D'ailleurs, j'étais conforté par cette idée, au vu d'un post de Ayende quand celui ci cherchait une base de donnée embarquée.

 

En fait il existe une solution.

Sql Server CE accepte le multi threading, à partir du moment où chaque thread possède sa propre SqlCeConnection.

Mais me direz vous, ça parait normal que chaque opération crée une nouvelle Connection, réflexe d'utilisation de Sql Server et son pool de connexion.

Nous n'en avons pas sur Sql Server CE.

Et là où c'est balot, c'est que ce n'est ni Sql Server CE, ni mes Services asynchrones qui étaient en erreur .. mais Linq to Sql et ma petite classe static Manager, qui est chargée de me rapatrier une connexion SqlCeConnection.

Pour expliquer ce geste de folie, un DataContext Linq nécessite en constructeur une connexion (SqlCeConnection dans mon cas)

J'avais donc écrit un truc du genre:

  private static SqlCeConnection configurationDbConnection;
 /// <summary>
///
Get Connection From Configuration File if any
/// </summary>
public static SqlCeConnection ConfigurationDbConnection
    {
        get
     
{
            if(configurationDbConnection != null)
                return configurationDbConnection;

            ConnectionStringSettings connection =

           ConfigurationManager.ConnectionStrings["LocalDatabaseConnection"] 
           as ConnectionStringSettings;

     if (connection == null && ConfigurationManager.ConnectionStrings.Count > 1 &&
        ConfigurationManager.ConnectionStrings[1].ProviderName == "Microsoft.SqlServerCe.Client.3.5")
        connection = ConfigurationManager.ConnectionStrings[1] as ConnectionStringSettings;

     if (connection == null) 
       throw new ApplicationException("No Connection String in application configuration file");


     configurationDbConnection = new SqlCeConnection(connection.ConnectionString);

     return configurationDbConnection;
 }
}

 

On voit que je stocke la connection créée dans une variable statique.

Monumentale ERREUR !!

Chacun des threads créant son propre DataContexte, va utiliser la MÊME Connexion qu' un autre thread (static inside)

Pour être sûr que chaque DataContexte crée sa propre connexion, il suffit juste d' appeler son constructeur avec un paramètre non pas une connexion, mais juste une chaîne de connexion.

Un petit coup de reflector, nous montre qu'il va alors bien créer une nouvelle connexion :

image

provider.CreateConnection() appellant le DbProviderFactory de SqlServerCe, celui ci retournant un joli new SqlCeConnection().

Du coup, mon petit code de mon Manager devient :

private static ConnectionStringSettings connectionStringSettings;
/// <summary>
/// Get Connection From Configuration File if any
/// </summary>
public static SqlCeConnection GetConfigurationDbConnection()
{
    if (connectionStringSettings == null)
    {
        connectionStringSettings = 
                  ConfigurationManager.ConnectionStrings["LocalDatabaseConnection"] 
                  as ConnectionStringSettings;

        if (connectionStringSettings == null && ConfigurationManager.ConnectionStrings.Count > 1 &&
                ConfigurationManager.ConnectionStrings[1].ProviderName == "Microsoft.SqlServerCe.Client.3.5")
            connectionStringSettings = ConfigurationManager.ConnectionStrings[1] as ConnectionStringSettings;

        if (connectionStringSettings == null) 
           throw new ApplicationException("No Connection String in application configuration file");
    }

    return new SqlCeConnection(connectionStringSettings.ConnectionString);
}

Et là, miracle (ou pas ) tout fonctionne...

Conclusion

Alors, pour faire du multi threading avec Sql Server CE, pensez à une chose importante :

Chaque Thread doit avoir sa propre connexion.

A partir de là, tout roule.

Et si on reprend les caractéristiques de Sql Server CE, on peut créer 256 connexion simultanées, ce qui vous laisse de la marge !

Sql Server CE, Multi Threads, Multi User

Et voilà, je viens d'y passer une journée (encore ...) et non, Sql Server CE, n'est PAS multi thread safe...

Alors voilà soyons clair :

Feature

SQL CE

Size of total deployment

1,834 KB

Number of concurrent connections

256

Concurrent process connections

1

Database Size Limit

4GB

Max CPUs Supported

1

Alors la conclusion est consternante, mais :

  • Oui, vous pouvez ouvrir plein de connections simultannément
  • Non, vous ne pouvez pas ouvrir 2 connections sur 2 threads séparés..

Maintenant, un exemple que je viens de faire.

Une petite application où je teste 3 cas. Dans chacun des cas, je fais 1 appels sur 2 services différents. Chaque service me renvoyant des données provenant d'une base de donnée Sql Server CE.

les 3 cas que je veux tester :

  1. Apel des 2 services en synchrone, histoire de vérifier que ça marche dans un cas classique mono thread
  2. Appel de mes 2 services par WCF, en monde Asynchrone (via les proxys générés et leurs appels asynchrones générés)
  3. Appel de mes 2 services directement, et utilisation de BackgroundWorkers

L'application est architecturée de manière classique.

  1. Une UI
  2. Un Service WCF
  3. Un projet Entité, partagé par le service et l'UI

 image

Je fais un peu de WCF, mes services sont exposés et je génère les proxys des services via un "Add Service Reference" :

image

Note : Je demande à générer les appels asynchrones, sinon aucun intéret de tester le multi-threading :)

Petit Hack dans mon architecture : Pour le 3eme cas, je référence directement mon assembly service dans mon projet UI (Rhoooo c'est pas bien; mais c'est pour l'exemple :))

Pour information, j'ai utilisé Linq To Sql pour générer le dbml dans le projet Entités, mais pour être absolument sûr du résultat, j'utilise une bonne vielle connection ADO.NET et je génère les commandes à la main.

Cas 1 : WCF Synchrone

Je crée mes 2 services (mes proxys) :

// For Exemple 1 and 2 : use of WCF Services
public ClientProxy.ClientServicesClient ClientServices { get; set; }
public ParameterProxy.ParameterServicesClient ParameterServices{ get; set; }

Je les instancie au démarrage :

ClientServices = new SqlServerCE_WCFAsync.ClientProxy.ClientServicesClient();
ParameterServices = new SqlServerCE_WCFAsync.ParameterProxy.ParameterServicesClient();

J'appel mes 2 services :

this.dataGridView1.DataSource  = ClientServices.GetClients2();

var p = ParameterServices.GetParameter("AddressMail");
if (p != null)
{
    label1.Text = p.Value;
}

Bon, ben ça, ça fonctionne...

Continuons..

Cas 2 : WCF Asynchrone

Mes 2 services sont créés (voir Cas 1)

Je configure le service Client, pour qu'il utilise son proxy de manière asynchrone :

ClientServices.GetClients2Completed += ClientServices_GetClients2Completed;

je crée la méthode Completed :

void ClientServices_GetClients2Completed(object sender, 
             SqlServerCE_WCFAsync.ClientProxy.GetClients2CompletedEventArgs e)
  {
      this.dataGridView1.DataSource = e.Result;
  }

Et je tente d'appeller ce service en asynchrone, alors que j'appel le service Parameter en synchrone lui :

ClientServices.GetClients2Async();
var p = ParameterServices.GetParameter2("AddressMail");
if (p != null)
{
    label1.Text = p.Value;
}

Et là ça se vautre lamentablement avec une exception qui ferait frémir un mamouth congelé :

"

A first chance exception of type 'System.AccessViolationException' occurred in System.Data.SqlServerCe.dll
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

"

Cas 3 : Utilisation de BackGroundWorker

Ici, je me passe de WCF (et donc je référence directement l'assembly service dans mon projet UI)

et je tente d'utiliser un BackGroundWorker sur le service ClientServices et je fais un appel direct sur le service ParameterServices

Configuration du BackGroundWorker :

bgw1 = new BackgroundWorker();
bgw1.DoWork += new DoWorkEventHandler(bgw1_DoWork);
bgw1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw1_RunWorkerCompleted);

Configuration du travail à effectuer :

void bgw1_DoWork(object sender, DoWorkEventArgs e)
 {
     SqlServerCE_WCFServices.ClientServices service = new SqlServerCE_WCFServices.ClientServices();

     e.Result = service.GetClients2();

 }

Configuration du completed du BackGround Worker :

void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
      if (e.Error != null)
          Trace.WriteLine("Erreur in User Interface in GetClients. Exception : " + e.Error.Message);

      if (e.Cancelled)
          Trace.WriteLine("Action cancelled in User Interface in GetClients");

      this.dataGridView1.DataSource = e.Result as List<Client>;

  }

Appel :

bgw1.RunWorkerAsync();

SqlServerCE_WCFServices.ParameterServices service = new SqlServerCE_WCFServices.ParameterServices();
var p = service.GetParameter2("AddressMail");
if (p != null)
{
    label1.Text = p.Value;
}

Et là, c'est le drame ...

"

A first chance exception of type 'System.AccessViolationException' occurred in System.Data.SqlServerCe.dll
Attempted to read or write protected memory. This is often an indication that other memory is corrupt
.

"

Conclusion

Alors voilà, conclusion:

  • Oui, utilisez un BackGround Worker pour faire des appels asynchrone et rendre votre application moins freezée et plus agréable
  • Non, n'utilisez pas d'appel asynchrone sur plusieurs threads...

Et là, je demande, comment faire avec WCF ?? Le monsieur génère un Thread par Service Proxy... c'est balôt !

Et je demande encore, comment faire une opération (de maintenance, ou de synchronisation) en arrière plan dans mon application desktop sous SQL Server CE ? Je demande à mon utilisateur d'être sympa et de pas faire de requêtes en attendant ??

Je laisse le code à disposition si le coeur vous tente :)

Posted: août 05 2008, 05:04 par Mimetis | avec 3 comment(s)
Classé sous :