I want to use SqlDependency to get notifications when some datas are changed by others applications using the database.
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();
}
}
}
}
So, with a single line i can register an event handler :
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();
}
But my problem is that the notification is immediatly fired :
--- ABONNEMENT ---
------------------------------ UPDATTEEEE -------------------------
Info: Query
Source: Statement
Type: Subscribe
That's why I commented AbonnementChanges in my event handler OnChanges (or it will loop infinitely).
I don't know where the problem comes from because I reset the notifications ({ Notification = null }) and my request respect the requirements (https://msdn.microsoft.com/en-us/library/ms181122.aspx).
Edit : I want to add that select * from sys.dm_qn_subscriptions returns nothing.
Edit : It looks like it comes from database configuration, and not from my implemention, as i tried another implemention which result in the same behaviour : http://www.codeproject.com/Articles/144344/Query-Notification-using-SqlDependency-and-SqlCach
Edit : I don't see where it comes from since i use SA which is sysadmin and have all rights, isn't it ?
Edit : I tried to define another connection to the database following this tutorial : http://www.codeproject.com/Articles/12862/Minimum-Database-Permissions-Required-for-SqlDepen
So i created 2 roles :
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]
and then i added the user (production) to this roles :
-- Making sure that my users are member of the correct role.
EXEC sp_addrolemember 'sql_dependency_starter', 'production'
EXEC sp_addrolemember 'sql_dependency_subscriber', 'production'
But with this connection i have the same behaviour than before. Notification are fired imediatly :
------------------------------ UPDATTEEEE -------------------------
Info: Query
Source: Statement
Type: Subscribe
Edit : I tried with simpler requests like : SELECT Nom, Prenom FROM dbo.Utilisateur.
Here are the details of the table which should be inspected :
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
As we can see there are some columns which can't be requested. That's why i don't use it.
Now let's check with SELECT Nom, Prenom FROM dbo.Utilisateur :
The projected columns in the SELECT statement must be explicitly
stated, and table names must be qualified with two-part names. Notice
that this means that all tables referenced in the statement must be
in the same database. OK
The statement may not use the asterisk () or table_name. syntax to
specify columns. OK
The statement may not use unnamed columns or duplicate column names. OK
The statement must reference a base table. OK
The projected columns in the SELECT statement may not contain
aggregate expressions unless the statement uses a GROUP BY
expression. When a GROUP BY expression is provided, the select list
may contain the aggregate functions COUNT_BIG() or SUM(). However,
SUM() may not be specified for a nullable column. OK
The statement may not specify HAVING, CUBE, or ROLLUP. A projected
column in the SELECT statement that is used as a simple expression
must not appear more than once. OK
The statement must not include PIVOT or UNPIVOT operators. OK
The statement must not include the INTERSECT or EXCEPT operators. OK
The statement must not reference a view. OK
The statement must not contain any of the following: DISTINCT,
COMPUTE or COMPUTE BY, or INTO. OK
The statement must not reference server global variables
(##variable_name). OK
The statement must not reference derived tables, temporary tables, or
table variables. OK
The statement must not reference tables or views from other databases
or servers. OK
The statement must not contain subqueries, outer joins, or
self-joins. OK
The statement must not reference the large object types: text, ntext,
and image. OK
The statement must not use the CONTAINS or FREETEXT full-text
predicates. OK
The statement must not use rowset functions, including OPENROWSET and
OPENQUERY. OK
The statement must not use any of the following aggregate functions:
AVG, COUNT(*), MAX, MIN, STDEV, STDEVP, VAR, or VARP. OK
The statement must not use any nondeterministic functions, including
ranking and windowing functions. OK
The statement must not contain user-defined aggregates. OK
The statement must not reference system tables or views, including
catalog views and dynamic management views. OK
The statement must not include FOR BROWSE information. OK
The statement must not reference a queue. OK
The statement must not contain conditional statements that cannot
change and cannot return results (for example, WHERE 1=0). OK
But that still doesn't works ... =(
Final edit - Solution : As Jon Tirjan said, it was caused by my computed column NomComplet which is not valid with the Service Broker (even when I don't ask to be notified on changes on this column, which is strange to me).
Service Broker doesn't work on tables with computed columns. You need to remove NomComplet from your table, or change it to an actual column which is populated another way (trigger, stored procedure, etc.)
The notification is being fired immediately because an error occurs while setting up the queue.
Thanks George Stocker for deleting my previous answer, but I had a serious issue with SqlDependency and I insist:
Be careful using SqlDependency class - it has the problems with memory leaks.
For my project I've used open source realization - SqlDependencyEx. It uses a database trigger and native Service Broker notification to receive events about the table changes. This is an usage example:
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);
With SqlDependecyEx you are able to monitor INSERT, DELETE, UPDATE separately and receive actual changed data (xml) in the event args object. Hope this help.
Related
I have the code like this
if (***Adapter.UpdateRow(memberID, Type) == 0)
{
***Adapter.InsertRow(memberID, Type);
}
but i get the Violation of PRIMARY KEY constraint error in the InsertRow.
the primary key in table is memberID and Type and here are the sqls:
InsertRow
string sql = #"
INSERT INTO TBL***** (
MemberID
, Type
, DateTime
)
VALUES (
#MemberID
, #Type
, GETDATE()
)
";
var cm = new SqlCommand(sql.ToString());
cm.Parameters.Add("#MemberID", SqlDbType.Int).Value = memberID;
cm.Parameters.Add("#Type", SqlDbType.Int).Value = (int)Type;
return this.ExecuteNonQuery(cm);
UpdateRow
string sql = #"
UPDATE TBL****
SET DateTime = GETDATE()
WHERE MemberID = #MemberID
AND Type = #Type
";
var cm = new SqlCommand(sql.ToString());
cm.Parameters.Add("#MemberID", SqlDbType.Int).Value = memberID;
cm.Parameters.Add("#Type", SqlDbType.Int).Value = (int)Type;
return this.ExecuteNonQuery(cm);
i'm really want to now the reason why it happened (right after the update one).
thank you all for your comments .
I think that maybe there are two threads that conflicted with each other ,then the first one inserted successfully but the second one failed.
I have this logic in my web site's user registration and it happends when the user click the activation link in the register mail.
I think the user may double click(with a high speed) the link and the error occured.
A Violation of PRIMARY KEY constraint means that you're attempting to insert a record that will create a duplicate on the primary key fields.
Check the table definition and note the fields in the primary key.
If you can post the table definition in your question for other readers even better :)
Edit after comment added
Given your primary key contains both MemberId and Type, there is likely a logic error in the update, or there is a scenario where no results are returned in the update.
You should use an upsert (update or insert) as Allan S. Hansen has suggested.
The logic is as follows:
Check if the record exists
Either update if exists or insert a new record
You wouldn't normally perform an update to check if the record exists, as the update may perform no changes in some scenarios.
Your code is relying on the ROWCOUNT being returned after executing DML. This is not guaranteed to happen, because is subject to SET NOCOUNT setting. When SET NOCOUNT is ON
the returned rowcount is always 0, irrelevant how many rows where actually updated. The returned rowcount is what ExecuteNonQuery() returns.
You could check and ensure SET NOCOUNT is OFF but it would still be a lost cause. The code you propose is fundamentally flawed in presence of concurrency: multiple client apps can run the UPDATE simultaneously and all conclude they should INSERT and then only one would succeed. The correct way to do this is to use MERGE statement:
MERGE INTO TBL**** as t
USING (VALUES (GETDATE(), #MemberId, #Type)) AS s (date, MemberID, Type)
ON t.MemberID =s.MemberID and t.Type = s.Type
WHEN MATCHED THEN
UPDATE SET t.DateTime = s.date
WHEN NOT MATCHED BY TARGET
INSERT (MemberID, Type, DateTime) VALUES (s.MemberID, s.Type, s.DateTime);
This is a single statement that does wither the INSERT or the UPDATE without the concurrency risks.
I'm using EntityObjects in C# and my backend is SQLite. System information:
Windows XP (up-to-date), VS2010 Premium, System.Data.SQLite 1.0.88.0 (3.7.17)
The table schema is:
CREATE TABLE [transaction] (
[primary_key_col] integer PRIMARY KEY AUTOINCREMENT,
[transaction_code] int NOT NULL,
[account_code] int NOT NULL,
[category_code] int NOT NULL,
[transaction_date] date NOT NULL,
[description] varchar(50));
public partial class Transaction : EntityObject represents this table.
To add a new Transaction, I call the following code:
Transaction transaction = new Transaction()
{
description = _desc,
transaction_code = generateHash(_desc),
account_code = _acccode,
category_code = _catcode,
transaction_date = DateTime.Now
};
dbEntities.Transactions.AddObject(transaction);
dbEntities.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
Works fine but after adding a few rows, I get the following exception:
Constraint failed\r\nColumn account_code is not unique.
If I exit the application and restart it, this error goes away but then randomly comes back again.
I know account_code is not unique, I never asked for it to be unique!!!
There are only 3 specific ways the application can create a transaction and this exception is thrown rather randomly from any of these methods. Is this something to do with SQLite? (I'm new to SQLite). Any idea why this is happening? I've nearly pulled out all my hair ...
As per comment which seems to have done the trick:
Are you using a "new" dbEntities context every time you do an insert or are you re-using the same one for multiple inserts? It shouldn't matter either way but I would try the former if you are currently using the latter.
I have a table that stores rejected contract proposals.
CREATE TABLE "STATUS_CONTRATO" (
"STC_ID" NUMBER NOT NULL,
"CTB_CONTRATO" NUMBER NOT NULL,
"STC_DATA" DATE NOT NULL,
"STC_OBSERVACAO" VARCHAR2(200) NOT NULL,
CONSTRAINT "STATUS_CONTRATO_PK"
PRIMARY KEY ( "STC_ID")
ENABLE
VALIDATE,
CONSTRAINT "FK_CONTRATO"
FOREIGN KEY ( "CTB_CONTRATO")
REFERENCES "CONTRATO" ( CTB_CONTRATO)
ON DELETE SET NULL
ENABLE
VALIDATE)
;
(Script generated by Visual Studio 2010)
This table has a simple Trigger, where the value of STC_ID is set:
TRIGGER "STATUS_CONTRATO_TRIGGER1"
BEFORE
INSERT
ON "STATUS_CONTRATO"
FOR EACH ROW
when (new.STC_ID = 0)
DECLARE
BEGIN
SELECT SEQ_STATUS_ID.NEXTVAL INTO :NEW.STC_ID FROM DUAL;
END;
SEQ_STATUS_ID is a simple sequence.
Here's my problem:
I can successfuly execute this insert in the VS2010 query window:
insert into myschema.STATUS_CONTRATO s(
s.STC_ID, s.CTB_CONTRATO, s.STC_DATA, s.STC_OBSERVACAO
)values(
0, 10, SYSDATE, 'Inserting by hand works'
);
But, when I try to insert using EF, I'm getting this exception:
System.Data.UpdateException: An error occurred while updating the entries.
See the inner exception for details. ---> Oracle.DataAccess.Client.OracleException:
ORA-01400: cannot insert NULL into ("MYSCHEMA"."STATUS_CONTRATO"."STC_ID")
ORA-06512: at line 4
I'm using this code to insert
STATUS_CONTRATO statusContrato = new STATUS_CONTRATO() {
STC_ID = 0,
CTB_CONTRATO = codContrato,
STC_DATA = DateTime.Today,
STC_OBSERVACAO = observacao
};
ent.STATUS_CONTRATO.AddObject(statusContrato);
ent.SaveChanges();
I'm using VS2010, Oracle 11g (CentOS Server), ODP.NET client 11.2.0.3.0 Production, .NET Framework 4.0, EF 4.
Check this: http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51odt-453447.html
Particularly, section "Triggers and Sequences"
From the Tools menu, select Run SQL Plus Script. Browse to the
location where you extracted the code and scripts, select the
triggers.sql script, select the HR connection from the list, and click
Run. The INSERTEMPLOYEES trigger created by the script generates a new
sequence for EMPLOYEE_ID whenever NULL is passed in for that value........
Your code works, except for a problem with
ent.UNV_STATUS_CONTRATO.AddObject(statusContrato);
Is UNV_STATUS_CONTRATO another table? Why is it not
ent.STATUS_CONTRATO.AddObject(statusContrato);
That should work just fine.
However, you might find it preferable to get the sequence value from your code and apply it to your ID column in memory before saving changes. So something like this:
public static int GetSequenceNextVal()
{
using (YourEntities entities = new YourEntities ())
{
var sql = "select myschema.SEQ_STATUS_ID.NEXTVAL from dual";
var qry = entities.ExecuteStoreQuery<decimal>(sql);
return (int)qry.First();
}
}
Then you call that method before SaveChanges().
Personally, I prefer to handle it this way so that my in-memory entity then also has the correct ID, instead of just having a 0, or having to query it right back out, etc.
Also, the method described here under Triggers and Sequences can supposedly wire up an entity's PK to a sequence.
You need to specify in your EF Mapping that the DB does not generate the key for you and EF should use the Key you provided...
see my post in the following thread:
https://stackoverflow.com/a/18635325/1712367
SQLite v3.7.5
Is there a way to enable SQLite Foreign Keys with cascade delete enabled by default?
Given the following example:
CREATE TABLE [Parent] (
[ParentId] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[Name] VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE [Child] (
[ChildId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[ParentId] INTEGER NOT NULL,
[Name] VARCHAR(50) NOT NULL,
FOREIGN KEY(ChildId) REFERENCES Child(ParentId) ON DELETE CASCADE
);
The only way I've been able to enable the cascade delete is to execute the PRAGMA foreign_keys = true command before a transaction:
using( var conn = new SQLiteConnection( _conn ) )
{
conn.Open();
var pragma = new SQLiteCommand( "PRAGMA foreign_keys = true;", conn );
pragma.ExecuteNonQuery();
var cmd = new SQLiteCommand( "Delete from Parent where ParentId = 1", conn );
cmd.ExecuteNonQuery();
}
Is there a setting on the database level that can be configured rather than having to call the pragma command before each transaction?
I've seen the triggers to enable cascade deletes, but am looking for something that would simply enable the PRAGMA foreign_keys = true at the database level.
System.Data.SQLite 1.0.66 doesn't have it, but in the repository version of it, they have updated to sqlite 3.7.4 and have made a new connection string attribute "Foreign Keys". Who knows when this will be released officially? So you could set this in your connection string. The project lives here now:
http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki
No, not even with compile-time options.
The only way, so far, is to use pragma foreign_keys=on at run time. The particular danger is that every application that touches the database has to do that.
If a specific application doesn't run that pragma statement, it can insert data that will violate foreign key constraints, and other applications won't know it. That is, turning on foreign keys doesn't warn you of existing data that violates the constraint.
No, there is no way currently (for backward compatibility). See the link you provided under (2):
Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection separately.
However, this might be changed in the future:
(Note, however, that future releases of SQLite might change so that foreign key constraints enabled by default. Careful developers will not make any assumptions about whether or not foreign keys are enabled by default but will instead enable or disable them as necessary.)
I can do this if I leave the Entity's Insert method as 'Use Runtime' and have it deal with the inserts directly, but I need to use an SP.
I have tried to simply return the ##Identity from sql server and then assign this to the ID but this causes a change notification and LinqToSql thinks it has changed, even though it's just the ID.
So basically I need a way of assigning the primary key on inserts via an sp, without it triggering the change notification.
Is this possible, if so how?
thanks.
A solution that works for me is to call the SP inside a partial method on your DataContext called InsertEntity (where Entity is the name of your class. In this method you must call your SP manually, but then you can map the return values to your entity's properties, and Linq To SQL will honour this.
For example:
public partial class YourDataContext
{
partial void InsertYourEntity(YourEntity entity)
{
using (DbCommand cmd = this.Connection.CreateCommand())
{
... // set the parameters and SP name here
cmd.ExecuteNonQuery();
entity.Id = (int) someParameter.Value;
}
}
}
Note that this will not suppress any IdChanging or IdChanged event (as it did change, and databindings can depend on a proper notification). However, calling SubmitChanges twice will not cause an Insert followed by an Update, as the second SubmitChanges will not see the entity as being changed.
With "Use runtime" set, entity will emit a sql statement like the one below, try to mimic it inside your stored procedure implementation.
DECLARE #output TABLE([SurrogateKey] UniqueIdentifier)
DECLARE #id UniqueIdentifier
INSERT INTO [identity].[AddressTypes]([Name])
OUTPUT INSERTED.[SurrogateKey] INTO #output
VALUES (#p0)
SELECT #id = SurrogateKey FROM #output
SELECT [t0].[SurrogateKey], [t0].[LastUpdatedOn], [t0].[LastUpdatedBy]
FROM [identity].[AddressTypes] AS [t0]
WHERE [t0].[SurrogateKey] = (#id)
-- #p0: Input VarChar (Size = 5; Prec = 0; Scale = 0) [Other]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1