2015-04-01 23 views
8

Desidero utilizzare SqlDependency per ottenere notifiche quando alcuni dati vengono modificati da altre applicazioni che utilizzano il database.SqlDependency si attiva immediatamente

public class DatabaseChangesNotification : IDisposable 
{ 
    private static string chaineDeConnexion = ConfigurationManager.ConnectionStrings["TransfertContext"].ConnectionString; 

    private static readonly Lazy<DatabaseChangesNotification> _instance = new Lazy<DatabaseChangesNotification>(() => new DatabaseChangesNotification()); 

    private DatabaseChangesNotification() 
    { 
     System.Diagnostics.Trace.WriteLine("--- SqlDependency START ---"); 
     SqlDependency.Start(chaineDeConnexion); 
    } 

    public void Dispose() 
    { 
     System.Diagnostics.Trace.WriteLine("--- SqlDependency STOP ---"); 
     SqlDependency.Stop(chaineDeConnexion); 
    } 

    public static DatabaseChangesNotification Instance 
    { 
     get 
     { 
      return _instance.Value; 
     } 
    } 

    public void AbonnerNotification(string requete, OnChangeEventHandler eventhandler) 
    { 
     using (SqlConnection connection = new SqlConnection(chaineDeConnexion)) 
     { 
      using (SqlCommand command = new SqlCommand(requete, connection) { Notification = null }) // clear existing notifications 
      { 
       connection.Open(); 

       var sqlDependency = new SqlDependency(command); 

       OnChangeEventHandler delegateAutoRemove = null; 
       delegateAutoRemove = (sender, e) => { 
        var dependency = sender as SqlDependency; 
        dependency.OnChange -= delegateAutoRemove; 
        eventhandler(sender, e); 
       }; 

       sqlDependency.OnChange += delegateAutoRemove; 

       command.ExecuteNonQuery(); 
      } 
     } 
    } 
} 

Così, con una singola linea posso registrare un gestore di eventi:

DatabaseChangesNotification.Instance.AbonnerNotification(@"SELECT IdUtilisateur, Code, Nom, Prenom, NomComplet, Login, Synchroniser FROM dbo.Utilisateur", OnChanges); 

    public void OnChanges(object sender, SqlNotificationEventArgs e){ 
     System.Diagnostics.Trace.WriteLine("------------------------------ UPDATTEEEE -------------------------"); 
     System.Diagnostics.Trace.WriteLine("Info: " + e.Info.ToString()); 
     System.Diagnostics.Trace.WriteLine("Source: " + e.Source.ToString()); 
     System.Diagnostics.Trace.WriteLine("Type: " + e.Type.ToString()); 

     GlobalHost.ConnectionManager.GetHubContext<TransfertClientHub>().Clients.All.hello("users modified !"); 
     //AbonnementChanges(); 
    } 

Ma il mio problema è che la notifica è immediatamente licenziato:

--- ABONNEMENT --- 
------------------------------ UPDATTEEEE ------------------------- 
Info: Query 
Source: Statement 
Type: Subscribe 

Ecco perché ho commentato AbonnementChanges nel mio event handler OnChanges (o loop infinitamente).

Non so da dove proviene il problema perché ho resettato le notifiche ({ Notification = null }) e la mia richiesta rispetta i requisiti (https://msdn.microsoft.com/en-us/library/ms181122.aspx).

Modifica: Desidero aggiungere che select * from sys.dm_qn_subscriptions non restituisce nulla.

Edit: Sembra che proviene dalla configurazione del database, e non dal mio L'implementazione, come ho provato un altro L'implementazione che provocano lo stesso comportamento: http://www.codeproject.com/Articles/144344/Query-Notification-using-SqlDependency-and-SqlCach

Edit: non vedo dove viene da quando uso SA che è sysadmin e ha tutti i diritti, non è vero?

Edit: ho cercato di definire un altro connessione al database seguendo questo tutorial: http://www.codeproject.com/Articles/12862/Minimum-Database-Permissions-Required-for-SqlDepen

Così ho creato 2 ruoli:

EXEC sp_addrole 'sql_dependency_subscriber' 
EXEC sp_addrole 'sql_dependency_starter' 

-- Permissions needed for [sql_dependency_starter] 
GRANT CREATE PROCEDURE to [sql_dependency_starter] 
GRANT CREATE QUEUE to [sql_dependency_starter] 
GRANT CREATE SERVICE to [sql_dependency_starter] 
GRANT REFERENCES on 
CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification] 
    to [sql_dependency_starter] 
GRANT VIEW DEFINITION TO [sql_dependency_starter] 

-- Permissions needed for [sql_dependency_subscriber] 
GRANT SELECT to [sql_dependency_subscriber] 
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [sql_dependency_subscriber] 
GRANT RECEIVE ON QueryNotificationErrorsQueue TO [sql_dependency_subscriber] 
GRANT REFERENCES on 
CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification] 
    to [sql_dependency_subscriber] 

e poi ho aggiunto l'utente (production) di questa ruoli:

- Assicurarsi che i miei utenti siano membri del ruolo corretto.

EXEC sp_addrolemember 'sql_dependency_starter', 'production' 
EXEC sp_addrolemember 'sql_dependency_subscriber', 'production' 

Ma con questa connessione ho lo stesso comportamento di prima. Notifica sono licenziati imediatly:

------------------------------ UPDATTEEEE ------------------------- 
Info: Query 
Source: Statement 
Type: Subscribe 

Edit: ho provato con le richieste più semplici come: SELECT Nom, Prenom FROM dbo.Utilisateur. Ecco i dettagli della tabella, che deve essere controllato:

SET ANSI_NULLS ON 
GO 

SET QUOTED_IDENTIFIER ON 
GO 

SET ANSI_PADDING ON 
GO 

CREATE TABLE [dbo].[Utilisateur](
    [IdUtilisateur] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [DF_Utilisateur_IdUtilisateur] DEFAULT (newid()), 
    [Code] [varchar](10) NOT NULL, 
    [Nom] [varchar](100) NOT NULL, 
    [Prenom] [varchar](100) NULL, 
    [NomComplet] AS (([Prenom]+' ')+[Nom]), 
    [Login] [varchar](50) NULL, 
    [Synchroniser] [bit] NOT NULL CONSTRAINT [DF_Utilisateur_Synchroniser] DEFAULT ((1)), 
    [DATE_CREATION] [datetime] NOT NULL CONSTRAINT [DF__Utilisate__DATE___2AA1E7C7] DEFAULT (getdate()), 
    [DATE_DERNIERE_MODIF] [datetime] NOT NULL CONSTRAINT [DF__Utilisate__DATE___2B960C00] DEFAULT (getdate()), 
    [Desactive] [bit] NOT NULL CONSTRAINT [DF_Utilisateur_Desactive] DEFAULT ((0)), 
CONSTRAINT [PK_Utilisateur] PRIMARY KEY CLUSTERED 
(
    [IdUtilisateur] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO 

SET ANSI_PADDING OFF 
GO 

Come possiamo vedere ci sono alcune colonne che non possono essere richiesti. Ecco perché non lo uso.

Ora controlliamo con SELECT Nom, Prenom FROM dbo.Utilisateur:

  • Le colonne proiettate in SELECT deve essere esplicitamente dichiarato , e nomi delle tabelle devono essere qualificati con i nomi in due parti.Si noti che significa che tutte le tabelle a cui si fa riferimento nella dichiarazione devono essere nello stesso database. OK
  • La dichiarazione non può utilizzare l'asterisco () o table_name. sintassi su specificare colonne. OK
  • L'istruzione non può utilizzare colonne senza nome o nomi di colonne duplicati. OK
  • La dichiarazione deve fare riferimento a una tabella di base. OK
  • Le colonne proiettate nell'istruzione SELECT non possono contenere espressioni aggregate a meno che l'istruzione non utilizzi un'espressione GROUP BY . Quando viene fornita un'espressione GROUP BY, l'elenco di selezione può contenere le funzioni aggregate COUNT_BIG() o SUM(). Tuttavia, SUM() non può essere specificato per una colonna nullable. OK
  • L'istruzione non può specificare HAVING, CUBE o ROLLUP. Una colonna proiettata nell'istruzione SELECT utilizzata come espressione semplice non deve apparire più di una volta. OK
  • La dichiarazione non deve includere operatori PIVOT o UNPIVOT. OK
  • La dichiarazione non deve includere gli operatori INTERSECT o EXCEPT. OK
  • L'istruzione non deve fare riferimento a una vista. OK
  • La dichiarazione non deve contenere nessuno dei seguenti elementi: DISTINCT, COMPUTE o COMPUTE BY o INTO. OK
  • L'istruzione non deve fare riferimento alle variabili globali del server (@@ nome_variabile). OK
  • L'istruzione non deve fare riferimento a tabelle derivate, tabelle temporanee o variabili di tabella . OK
  • L'istruzione non deve fare riferimento a tabelle o viste da altri database o server. OK
  • L'istruzione non deve contenere subquery, outer join o self-join. OK
  • L'istruzione non deve fare riferimento ai tipi di oggetto di grandi dimensioni: testo, ntext, e immagine. OK
  • L'istruzione non deve utilizzare i predicati full-text CONTAINS o FREETEXT . OK
  • L'istruzione non deve utilizzare le funzioni del set di righe, tra cui OPENROWSET e OPENQUERY. OK
  • L'istruzione non deve utilizzare nessuna delle seguenti funzioni di aggregazione: AVG, COUNT (*), MAX, MIN, DEV.ST, STDEVP, VAR o VARP.OK
  • La dichiarazione non deve utilizzare funzioni non deterministiche, tra cui posizionamento e funzioni di finestre. OK
  • L'istruzione non deve contenere aggregati definiti dall'utente. OK
  • L'istruzione non deve fare riferimento alle tabelle di sistema o alle viste, incluse le viste del catalogo e le viste di gestione dinamica . OK
  • La dichiarazione non deve includere PER INFORMAZIONI DI SFOGLIA. OK
  • L'istruzione non deve fare riferimento a una coda. OK
  • L'istruzione non deve contenere istruzioni condizionali che non possono cambiare e non possono restituire risultati (ad esempio, WHERE 1 = 0). OK

Ma questo ancora non funziona ... = (

montaggio finale - Soluzione: Come ha detto Jon Tirjan, è stato causato da mia colonna calcolata NomComplet che non è valido il Service Broker (anche se non chiedo di essere informato sulle modifiche su questa colonna, che è strano per me)

+0

* Voglio aggiungere che 'select * da sys.dm_qn_subscriptions' non restituisce nulla * Beh sì,' SqlDependency' annulla l'iscrizione una volta ricevuta una notifica. Il tuo evento è stato licenziato, nessun abbonamento in più. –

+0

Esattamente. Quello che ho capito anche io. –

+0

Si prega di non [cross post this] (http://dba.stackexchange.com/questions/96886/sqldependency-fires-immediately) la prossima volta su DBA.SE. – LowlyDBA

risposta

5

Service Broker non funziona su tabelle con colonne calcolate. È necessario rimuovere NomComplet dal tuo tabella, o cambiarlo in una colonna effettiva che viene popolata in un altro modo (trigger, stored procedure, ecc.)

La notifica viene avviata immediatamente perché si verifica un errore durante l'impostazione della coda.

+1

Risposta interessante grazie. Controllerò ma non sono a casa questo fine settimana. –

+0

Sì! È davvero il problema. Grazie ! –

0

Grazie George Stocker per cancellare la mia risposta precedente, ma ho avuto un grave issue con SqlDependency e insisto:

Fare attenzione usando SqlDependency classe - ha il problems con perdite di memoria.

Per il mio progetto ho utilizzato la realizzazione open source - SqlDependencyEx. Utilizza un trigger del database e una notifica nativa di Service Broker per ricevere eventi relativi alle modifiche della tabella. Questo è un esempio di utilizzo:

int changesReceived = 0; 
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
      TEST_CONNECTION_STRING, TEST_DATABASE_NAME, TEST_TABLE_NAME)) 
{ 
    sqlDependency.TableChanged += (o, e) => changesReceived++; 
    sqlDependency.Start(); 

    // Make table changes. 
    MakeTableInsertDeleteChanges(changesCount); 

    // Wait a little bit to receive all changes. 
    Thread.Sleep(1000); 
} 

Assert.AreEqual(changesCount, changesReceived); 

Con SqlDependecyEx voi sono in grado di monitorare INSERT, DELETE, UPDATE separatamente e ricevere reale i dati modificati (xml) in caso args oggetto. Spero che questo aiuto.

Problemi correlati