24 janvier 2017

c# Service Stack et transactions

Je me suis fait avoir récemment donc pour ne pas oublier : Voici comment utiliser les transactions avec la librairie ServiceStack.


Une classe Tache à une méthode InsertToDb qui à pour but d’insérer l'objet et ses fils des Actions dans une base de données.

  private void InsertToDb(IDbConnection cnx)
    {
      this.Id = Convert.ToInt32(cnx.Insert<Tache>(this, selectIdentity: true));

      // insertion des actions
      foreach (Action act in this.Actions)
      {
        act.TacheId = this.Id;
        act.ToDB(cnx);
      }
    }

Pour rendre le code transactionnel il faut lancer une transaction : le réflexe est la méthode BeginTransaction() de la classe IDbConnection.
Et bien avec ServiceStack ça marche pas avec le  select identity, il semble que la transaction soit mal initialisée. Pour y arriver facilement il faut utiliser la méthodes d'extension : OpenTransaction() Qui fait la même chose sans générer d'erreur !

     using (FConnexion cnn = new FConnexion())
      {
        var tran = cnn.Db.OpenTransaction();
        try
        {
          // mettre en base la tâche
          t.ToDB(cnn.Db);
          tran.Commit();
        }
        catch (Exception ex)
        {
          tran.Rollback();
          throw ex;
        }
      }

Ne pas oublier le using qui va bien :
using ServiceStack.OrmLite;

La classe FConnection gère une propriété IDbConnection Db et se charge d'ouvrir la connexion à la base.
using System;
using System.Data;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.SqlServer;

  /// <summary>
  /// Class base pour gestion connexion.
  /// </summary>
  public class FConnexion : IDisposable
  {
    /// <summary>
    /// Initialise une nouvelle instance de la classe <see cref="FConnexion" />.
    /// </summary>
    public FConnexion()
    {
      var dbfactory = new OrmLiteConnectionFactory(System.Configuration.ConfigurationManager.ConnectionStrings["MaBase"].ToString(), SqlServerOrmLiteDialectProvider.Instance);
      this.Db = dbfactory.Open();
      this.Db.Open();
    }

    /// <summary>
    /// Obtient ou définit La connexion
    /// </summary>
    public IDbConnection Db { get; private set; }

    /// <summary>
    /// Libère les ressources de l'objet de connexion.
    /// </summary>
    public void Dispose()
    {
      this.Db.Dispose();
    }
  }


Enjoy !

23 janvier 2017

SQL : Premier jour du mois et dernier jour du mois

Voici ce que j'utilise pour trouver le premier jour du mois d'une date donnée :

DECLARE @dt DATE = '2017-02-17'

SELECT  @dt, DATEADD(DAY, 1 - DATEPART(DAY, @dt), @dt)

Ou
SELECT  @dt, DATEADD(MONTH, DATEDIFF(MONTH, 0, @dt), 0)


Ce qui donne dans les deux cas :
17/02/2017     01/02/2017

Dans le premier exemple : on retire le nombre de jour moins 1 à la date en cours.
Dans le second : on ajoute à la date "0" la différence de mois entre "0" et la date fournie



Voici ce que j'utilise pour trouver le dernier jour du mois d'une date donnée :

DECLARE @dt DATE = '2017-02-17'
SELECT  @dt, DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, @dt) + 1, 0))


Ce qui donne  :
17/02/2017     28/02/2017

Explication : on ajoute un mois au premier jour du mois et on retire un jour.


MAJ 05/2021 : Sinon pour de le dernier jour du mois, il existe a maintenant (depuis SQL Server 2014!) la fonction EOMONTH().
On lui donne un jour dans le mois, elle renvoie le dernier jour du mois, simple, Merci SQL Server ! 

Enjoy !

28 novembre 2016

SQL : Savoir si on peut supprimer une ligne

Avant de savoir si l'on peut supprimer une ligne dans une base de données, il est bon de se poser la question : Est-elle référencée dans d'autres tables cette ligne ?

Pour se faire il suffit de suivre à l'envers les contraintes de foreign key de la base de données

voici une procédure utile :

CREATE PROCEDURE dbo.can_delete
  @table VARCHAR(100), @Id INT
AS
BEGIN
  SET NOCOUNT ON;
  DECLARE @Command VARCHAR(MAX

  SELECT @Command = ISNULL(@Command + ' UNION ALL ', '') + ' SELECT ''' + SCHEMA_NAME(tbl.schema_id) + '.' + tbl.name  +
    ''' AS [table] WHERE EXISTS(SELECT * FROM ' + SCHEMA_NAME(tbl.schema_id) + '.' + tbl.name
    + ' WHERE ' + col.name + ' = ' + CAST(@Id AS VARCHAR) + ')' 
  FROM sys.foreign_key_columns fkc
  INNER JOIN sys.tables tbl ON fkc.parent_object_id = tbl.object_id
  INNER JOIN sys.columns col ON fkc.parent_object_id = col.object_id AND fkc.parent_column_id = col.column_id
  WHERE OBJECT_NAME(fkc.referenced_object_id) = @table

  EXECUTE (@Command);
END


A Noter dans cette procédure :
On créer une variable @commande qui va contenir une requête SQL construite dynamiquement et qui renvéra le résultat. Noter que le paramètre @command de la commande EXECUTE est entre parenthèses.

La requête fait appel à la table système de SQL SERVER ; sys.foreign_key_column qui maintient les Foreign Keys de la base de données

ATTENTION : Cette procédure ne fonctionne que si la clé primaire de la table concernée, n'est formé que d'une seule colonne (en général un entier auto incrémenté)

31 octobre 2016

ServiceStack DataAnotation

ServiceStack est une librairie fort utile que je ne presente pas ici...
Petit mémo de la partie DataAnotation qui permet de faire un mapping Relationnel / Objet très simplement.


L'attribut :  [Alias("")] : Permet de mapper une table (s'il est mis sur une classe) ou une colonne s'il est mis sur une propriété.
La propriété doit toujours être de la forme : public [type] [propriété] { get; set;}
Attention aux types mappés : n'utiliser que les types valeur de base (int, double, decimal (pour les money  Sql Server), string, bool).
Vous pouvez utilisez les types nullables (int?, double?, decimal?, string, bool?)


L'attribut : [Ignore] : Permet d'ajouter des propriétés à l'objet sans les mapper à une colonne.
Exemple pour les enum je câble souvent une propriété :

public enum ValeursPossible
{
   val1 = 1,
   val2 = 2,
  ....
}

[Alias("maColonne")]
public int MonEnumInt {get; set;}

[Ignore]
public ValeursPossible MonEnum
{
   get
   {
      return (ValeurPossible)this.MonEnumInt;
   }
   set
  {
     this.MonEnumInt = (int)value;
  }
}

L'attribut : [Autoincr] est très important pour la propriété qui mappe une colonne auto-incrémentée de Sql Server : A ne pas oublier !


Enfin : Très utile !!
L'attribut : [Schema("xxx")] permet de mapper des tables qui ne sont pas dans le schéma par défaut (DBO pour Sql Server).



02 mai 2016

SQL Server : Indexer le colonnes de foreign key systématiquement

Une pratique qui permet d'obtenir un bon premier résultat lors de l’indexation d'une base de données SQL Server est de créer les indexes correspondants aux colonnes de clés étrangères.
En effet lorsque l'on fait des requêtes avec des jointures, d'un côté, la colonne de clé primaire auquel on accède est indexée par l'index de clé primaire, mais de l'autre, la colonne de clé étrangère elle, si elle n'est pas indexée, SQL Server doit parcourir l'ensemble des données.

Attention cette méthode est une première approche. Elle ne doit pas empêcher l'analyse des requêtes et structures des tables pour trouver les meilleurs index en fonction des contextes connus.

Voici ma requête qui me permet de trouver et créer les index absents lorsque la structure de la base a changé :

SELECT t.*
, 'IF (NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N''[' + t.parent_schema_name + '].[' + t.parent_table_name + ']'') AND name = N'''+ t.index_name + '''))
BEGIN
   CREATE NONCLUSTERED INDEX [' + t.index_name  + '] ON [' + t.parent_schema_name + '].[' + t.parent_table_name + '
(
[' + t.parent_column_name + '] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
END
' AS requete
FROM (
        SELECT fk.Name AS foreign_key_name, cfk.constraint_column_id,  pos.name AS parent_schema_name, po.name AS parent_table_name, cpo.name AS parent_column_name
         , oc.name AS referenced_table_name, cpc.name AS referenced_column_name
         , 'idx_' + LOWER(pos.name) + '_' + LOWER(po.name) + '_colonne_fk_' + LOWER(cpo.name) AS index_name
        FROM sys.foreign_keys fk
        INNER JOIN sys.foreign_key_columns cfk ON fk.object_id = cfk.constraint_object_id
        INNER JOIN sys.objects po ON cfk.parent_object_id = po.object_id
        INNER JOIN sys.columns cpo ON cpo.object_id = po.object_id AND cpo.column_id = cfk.parent_column_id
        INNER JOIN sys.schemas pos ON po.schema_id = pos.schema_id
        INNER JOIN sys.objects oc ON cfk.referenced_object_id = oc.object_id
        INNER JOIN sys.columns cpc ON cpc.object_id = oc.object_id AND cpc.column_id = cfk.referenced_column_id
     ) t
LEFT JOIN (SELECT ix.name AS index_name, tx.name AS table_name, sx.name AS schema_name, ix.object_id
FROM sys.indexes ix
INNER JOIN sys.tables tx ON ix.object_id = tx.object_id
           INNER JOIN sys.schemas sx ON tx.schema_id = sx.schema_id
) i ON i.schema_name = t.parent_schema_name
             AND i.table_name = t.parent_table_name
             AND i.index_name = t.index_name
WHERE i.object_id IS NULL

Le colonne "requete" une fois sélectionnées et exécutée crée les index manquants.
Ajuster le texte en fonction des besoins (surtout par rapport aux options de l'index créé)

06 décembre 2015

Expressions régulières pour parser du XLM

J'ai voulu de manière simple mettre en surbrillance une string XML pour un rendu dans une page HTML !


Bon même y a pleins de plug-in c# ou JS, voir des supers convertisseurs en ligne... je préfère me braquer cela à la main : voici ma proposition...

Il y a plusieurs types de "balises" à détecter, entourer dans le texte d'origine ces balises de <span class="Type Balise">xx</span> pour gérer cela avec du CSS.

Voici les expressions régulières associées aux types de balises :

Les commentaires : (?<comment><!--(\w|\W)*?-->)
Les tags : (<|</)(?<tag>\w*?)([>\s])
Les attributs : ((<\w*?\s*?)|(("|')\s*?))(?<attr>\w*)=("|')
Les valeurs des attributs : (=(?<quote>"|'))(?<attvalue>(\w|\W)*?)?(\k<quote>) 
Les valeurs des tags : (?<value>(\w|\W)*?)<   [Penser à éliminer les valeurs vide ici]
Les séparateurs : (?<sep><|>|=|"|')


Détecter les infos dans l'ordre et éliminer des résultats les infos dont le texte est déjà dans un groupe précédent (Par exemple pour éviter de mettre en surbrillance un tag dans un commentaire).


La suite quand j'ai le c# qui marche !!

14 novembre 2015

SQL : Recherche dans le contenu des procédures, vues, ou fonctions

Vous recherchez un texte dans tous les codes des procédures, fonctions ou vues SQL Server voici une requête qui permet de faire cela...

C'est utile dans le cas suivant ;
On change les paramètres d'une fonction ou procédure. Ou est appelée cette fonction ? Quels sont les impacts de mon changement ?


La procédure utilise largement le schéma système sys de SQL Server avec les tables (ou vues) :

  • sys.all_objects : qui liste les objets qui nous interresse ici
  • sys.schemas pour avoir les noms des schémas
  • sys.sql_modules pour avoir le contenu des procédures
  • sys.system_sql_modules pour avoir le contenu des procédure systèmes s'il y a lieu

La table sys.all_objects : dispose d'une colonne type qui indique de quoi on parle :

  • 'P' ou 'PC pour une procédure
  • 'FN' ou 'TF' ou 'IF' pour une fonction
  • 'V' pour une vue

En enlevant ces filtres on s'assure d'avoir tous les objets de la base. Cela peut être utile en fonction des besoins


Le code SQL :

DECLARE @txt VARCHAR(MAX) = 'xxx'   --- Texte à chercher

SELECT *

    FROM  (

      SELECT sp.object_id AS [id]

         , CASE sp.type WHEN 'AF' THEN 'Fonction d''agrégation (CLR)'

                        WHEN 'C'  THEN 'Contrainte CHECK'

                        WHEN 'D'  THEN 'DEFAULT (contrainte ou autonome)'

                        WHEN 'F'  THEN 'Contrainte FOREIGN KEY'

                        WHEN 'FN' THEN 'Fonction scalaire SQL'

                        WHEN 'FS' THEN 'Fonction scalaire d''assembly (CLR)'

                        WHEN 'FT' THEN 'Fonction table d''assembly (CLR)'

                        WHEN 'IF' THEN 'Fonction table en ligne SQL'

                        WHEN 'IT' THEN 'Table interne'

                        WHEN 'P'  THEN 'Procédure stockée SQL'

                        WHEN 'PC' THEN 'Procédure stockée d’assembly (CLR)'

                        WHEN 'PG' THEN 'Repère de plan'

                        WHEN 'PK' THEN 'Contrainte PRIMARY KEY'

                        WHEN 'R ' THEN 'Règle (ancienne, autonome)'

                        WHEN 'RF' THEN 'Procédure de filtre de réplication'

                        WHEN 'S ' THEN 'Table de base système'

                        WHEN 'SN' THEN 'Synonyme'

                        WHEN 'SO' THEN 'Objet séquence'

                        WHEN 'SQ' THEN 'File d''attente du service'

                        WHEN 'TA' THEN 'Déclencheur d''assembly DML (CLR)'

                        WHEN 'TF' THEN 'Fonction table SQL'

                        WHEN 'TR' THEN 'Déclencheur DML SQL'

                        WHEN 'TT' THEN 'Type de table'

                        WHEN 'U'  THEN 'Table (définie par l''utilisateur)'

                        WHEN 'UQ' THEN 'Contrainte UNIQUE'

                        WHEN 'V'  THEN 'Vue'

                        WHEN 'X'  THEN 'Procédure stockée étendu'

            ELSE sp.type END AS [type]

        , s.[name] AS [schema], sp.[name] AS [nom]

        , ISNULL(smsp.[definition], ssmsp.[definition]) AS [code_sql]

       FROM sys.all_objects AS sp

       INNER JOIN sys.schemas AS s ON sp.schema_id = s.schema_id

       LEFT OUTER JOIN sys.sql_modules AS smsp ON smsp.object_id = sp.object_id

       LEFT OUTER JOIN sys.system_sql_modules AS ssmsp ON ssmsp.object_id = sp.object_id

       WHERE CAST(CASE WHEN sp.is_ms_shipped = 1 THEN 1

                       WHEN (SELECT p.major_id

                             FROM sys.extended_properties AS p

                              WHERE p.major_id = sp.object_id

                                AND p.minor_id = 0

                                AND p.class = 1

                                AND p.name = N'microsoft_database_tools_support'

                             ) IS NOT NULL THEN 1

                       ELSE 0 END AS bit) = 0

         AND smsp.execute_as_principal_id IS NULL

    ) pfv

    WHERE pfv.code_sql LIKE '%' + @txt + '%'

    ORDER BY [type], [schema], [nom]



Enjoy

ADD 07-2018 
Manière plus simple d'obtenir le code d'une procédure / fonction / vue / trigger /...  (ça ne marche pas pour les tables !)
SELECT object_definition(object_id('[Nom schéma].[Nom Objet]')) AS [Definition] FOR XML  PATH('')

La clause FOR XML évite que SSMS tronque le résultat !
Plus simple
A adapter dans ce qui précède si besoin d'élargir les recherches

EDIT 08/2021
Mise à jour de la procédure pour remonter tous les cas possible de code.