Sql Cache dependency in Asp.Net - c#

I m loading table data into cache .And binding it to dropdown .But if there any new value added in datatable then that cache should invalid and new cache should get create.I am using Asp.Net 3.5 ,linq to SQL
Folloing is my .aspx code
public List<Employee> GetEmployeeData()
{
//get from cache
object obj = HttpRuntime.Cache.Get(Constants.Constants.cacheEmployee);
if (obj != null)
{
return obj as List<Employee>;
}
else // get from database
{
using (TestdbEntities entities = new TestdbEntities(connectionString))
{
try
{
IEnumerable<Employee> employee= entities.Employees.OrderBy(l => l.EmployeeName);
System.Web.Caching.SqlCacheDependencyAdmin.EnableNotifications(connectionString);
if (!System.Web.Caching.SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(connectionString).Contains ("master.Employee"))
System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, "master.Employee");
System.Web.Caching.SqlCacheDependency sqldep = new System.Web.Caching.SqlCacheDependency(connectionString, "master.Employee"); ;
HttpRuntime.Cache.Insert(Constants.Constants.cacheEmployee, employee.ToList(), sqldep);
return employee.ToList();
}
catch (Exception ex)
{
throw ex;
}
}
}
}
But i am getting following error
Please make sure the database name and the table name are valid. Table names must conform to the format of regular identifiers in SQL.
Please suggest

The other answer is correct that the "." in the table name is the issue. For some reason the AspNet_SqlCacheRegisterTableStoredProcedure wasn't written to handle different schemas.
I must credit this link with cluing me into the issue:
http://forums.asp.net/t/1249281.aspx?SQL+Cache+Dependency+with+custom+schema
However, I came up with my own re-write of the stored proc below that is a little better :) It's a little cleaner, handles the situation even if [ are included in the name, and also keeps the schema name in the AspNet_SqlCacheTablesForChangeNotification table in case you have the same table name in two different schemas.
DECLARE #triggerName AS NVARCHAR(3000)
DECLARE #fullTriggerName AS NVARCHAR(3000)
DECLARE #canonTableName NVARCHAR(3000)
DECLARE #quotedTableName NVARCHAR(3000)
DECLARE #schemaName NVARCHAR(3000)
DECLARE #schemaAndTableName NVARCHAR(3000)
--Replace these if they are in the param, we'll add them back later for the #canonTableName only
SET #tableName = REPLACE(REPLACE(#tableName, '[', ''), ']', '')
IF(CHARINDEX('.',#tableName) <> 0)
BEGIN
SET #schemaName = SUBSTRING(#tableName,0,CHARINDEX('.',#tableName))
SET #tableName = SUBSTRING(#tableName,CHARINDEX('.',#tableName) + 1,LEN(#tableName) - CHARINDEX('.',#tableName))
SET #schemaAndTableName = #schemaName + '.' + #tableName
END
ELSE
BEGIN
SET #schemaName = 'dbo' --If the param doesn't have a schema, use the default
SET #schemaAndTableName = #tableName --If they didn't pass in the schema, we don't want it in the AspNet_SqlCacheTablesForChangeNotification table
END
SET #triggerName = #tableName + '_AspNet_SqlCacheNotification_Trigger'
SET #fullTriggerName = '[' + #schemaName + '].[' + #triggerName + ']'
SET #canonTableName = '[' + #schemaName + '].[' + #tableName + ']'
/* First make sure the table exists */
IF (SELECT OBJECT_ID(#schemaAndTableName, 'U')) IS NULL
BEGIN
RAISERROR ('00000001', 16, 1)
RETURN
END
BEGIN TRAN
/* Insert the value into the notification table */
IF NOT EXISTS (SELECT tableName FROM dbo.AspNet_SqlCacheTablesForChangeNotification WITH (NOLOCK) WHERE tableName = #schemaAndTableName)
IF NOT EXISTS (SELECT tableName FROM dbo.AspNet_SqlCacheTablesForChangeNotification WITH (TABLOCKX) WHERE tableName = #schemaAndTableName)
INSERT dbo.AspNet_SqlCacheTablesForChangeNotification
VALUES (#schemaAndTableName, GETDATE(), 0)
/* Create the trigger */
SET #quotedTableName = QUOTENAME(#schemaAndTableName, '''')
IF NOT EXISTS (SELECT name FROM sysobjects WITH (NOLOCK) WHERE name = #triggerName AND type = 'TR')
IF NOT EXISTS (SELECT name FROM sysobjects WITH (TABLOCKX) WHERE name = #triggerName AND type = 'TR')
EXEC('CREATE TRIGGER ' + #fullTriggerName + ' ON ' + #canonTableName +'
FOR INSERT, UPDATE, DELETE AS BEGIN
SET NOCOUNT ON
EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N' + #quotedTableName + '
END
')
COMMIT TRAN

The problem is the . in the table name master.Employee. The EnableTableForNotifications method calls the stored procedure AspNet_SqlCacheRegisterTableStoredProcedure in the database. Inside that is a call to the OBJECT_ID function, and I think that is parsing the table name as being a table called Employee in the master database. (When I first looked at this question, I was thinking the same way).
If you were to replace the . with an underscore _ in the table name, then your code will run correctly.
(You might also want to review your code in the case where the data isn't in the cache; data can disappear from the cache for any number of reasons but currently your code enables the database up for notifications every time, regardless of whether it's already enabled. The Single Responsibility Principle applies here to the GetEmployeeData method; it should only be concerned with getting data from the cache or database and not with setting up caching and notifications, that code should be broken out into a separate method.)

Related

SQL Server – pulling user data using dynamic query in stored procedure

I have problem, I'm having trouble viewing user information using stored procedure. The procedure accepts three parameters: table, column and searchBySomething. Every time I want to search for a user using another column, the column variable receives the column of the id and the searchBySomething variable receives specific id, the procedure is work but when I'm send another column I'm get the error message
Invalid column name (the data)
The procedure looks like this :
ALTER PROCEDURE [dbo].[userDetailsDisplay]
#table NVARCHAR(30),
#column NVARCHAR(30),
#searchBySomething NVARCHAR(30)
DECLARE #sql NVARCHAR(100)
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' + #searchBySomething
EXECUTE sp_executesql #sql
So the specific error you're getting is because you're not checking the input to see if the string being passed into #column actually exists. You can check for it's existence against the metadata catalog view sys.columns doing something like this:
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
However I would be remiss if I didn't point out two things.
First, this dynamic table dynamic where clause pattern is very bad practice. If it's for someone who already has database access, they can simply query the tables themselves. And if it's for an external user, well, you've basically given them full database read access through this procedure. Of course there are some rare occasions where this pattern is needed, so if you're dead set on using dynamic sql, that leads me to my next point.
The code you've written is vulnerable to a SQL injection attack. Any time you use dynamic SQL you must be VERY careful how it's constructed. Say I passed in the column name ; drop database [admin]-- Assuming you had such a database, my could would happily be executed and your database would disappear.
How to make dynamic SQL safe is a complex topic, but if you're serious about learning more about it, this is probably one of the best articles you can find. http://www.sommarskog.se/dynamic_sql.html
By parameterizing your query and using quotename() on the table and column, I modified it to look like this. This will still throw weird errors if someone tries to do an injection attack, but at least it wont actually execute their code.
create procedure [dbo].[userDetailsDisplay]
#table nvarchar(30),
#column nvarchar(30),
#searchBySomething nvarchar(30)
as
begin
declare
#sql nvarchar(max),
#params nvarchar(1000)
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
select #sql = '
select *
from ' + quotename(#table) + ' WHERE ' + quotename(#column) + ' = #searchBySomething'
execute sp_executesql
#stmt = #sql,
#params = '#searchBySomething nvarchar(30)',
#searchBySomething = #searchBySomething
end
Just check to make sure that the column exist in the table.
for each #table called, check that the #column variable is in that table.
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' +''' #searchBySomething +''''
Ex : select * from table where column ='value'

Unable to get Dynamic SQL to pass a dynamic table parameter

Not worried about SQL Injection or anything of the like, just trying to get this to work. Using SSMS and Visual Studio.
I have C# code that passes a variable, GlobalVariables.username, to an SQL parameter.
private void btnNext_Click(object sender, EventArgs e)
{
if (checkIntrotoPublicSpeaking.Checked || checkEffectiveOralCommunication.Checked || checkProfComm.Checked)
{
List<SqlParameter> sqlOralComm = new List<SqlParameter>();
sqlOralComm.Add(new SqlParameter("Username", GlobalVariables.username));
sqlOralComm.Add(new SqlParameter("IntrotoPublicSpeaking", cboxIntrotoPublicSpeaking.Text));
sqlOralComm.Add(new SqlParameter("EffectiveOralCommunication", cboxEffectiveOralCommunication.Text));
sqlOralComm.Add(new SqlParameter("ProfComm", cboxProfComm.Text));
DAL.ExecSP("CreateOralComm", sqlOralComm);
}
}
I've been reading into Dynamic SQL and saw that to pass the table name as a parameter, you have to construct it manually and execute it as "SET..." etc, etc. I've been trying slightly different modifications of the last 3 lines below. Each time, I'm greeted with an "invalid syntax near ..." exception pertaining to different parts of that line. In stack exchange it's broken into 3 lines but in SSMS it's one line, a little easier to read.
Status is nvarchar column and Course is an int column.
ALTER PROCEDURE [dbo].[CreateOralComm]
-- Add the parameters for the stored procedure here
#Username nvarchar(30),
#IntrotoPublicSpeaking nvarchar(3),
#EffectiveOralCommunication nvarchar(3),
#ProfComm nvarchar(3)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
END
GO
I know that global variable works, I have another line of code that's just a MessageBox displaying the value and it's correct. Just can't get those last few lines of SQL to work. I'm trying out just this first part, #IntrotoPublicSpeaking, before I move onto the other 2.
Any help would be really appreciated.
Two things here:
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
Missing comma before Status and I think you do need to use single quotes
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ', Status = ''Completed'' WHERE Course = 7600105';
EXEC sp_executesql #sql;

Write SQL code similar to a stored procedure inside my MVC project

I am looking into rewriting a stored procedure that returns an output parameter inside my code. Why I am doing this is because I am wanting to have a local stored procedure instead of having to call the stored procedure from MSSQL.
Is this possible? If so, then how?
I have looked into LINQ to Entities and started to realize that there is so little control of the queries. I need to find more definitive info, but from working with real stored procedures and using multiple queries, having an actual stored procedure would be more efficient with large amounts (meaning hundreds of thousands) of records because of pre-built items included with MSSQL and SPs. I am using this question as a reference for a future project when we want to have small-scale data being transferred from the front end of the site instead of using stored procedures.
This link is what I used to write my raw SQL code. I am using the primitive calls so I can return string variables.
http://www.entityframeworktutorial.net/EntityFramework4.3/raw-sql-query-in-entity-framework.aspx
A rough draft of my stored procedure looks like this:
I am using // for legibility purposes when viewing this answer. Please use -- when using this code.
CREATE PROCEDURE [dbo].[spSomething]
#FirstName varchar(25),
#LastName varchar(25),
#UserID varchar(25),
#RentalManID varchar(25),
#BranchAreaNum varchar(25),
#ReturnVal varchar(1) = '0' output
WITH RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CheckTblUserAdded varchar(25)
DECLARE #CheckTblUser varchar(25)
// ReturnVal = 0 means it failed to add user to table
// ReturnVal = 1 means it succeded in adding user to table
// ReturnVal = 2 means the user has already been added to the table
// Search for user inside tblUserAdded
SELECT #CheckTblUserAdded = [Window ID] FROM dbo.tblUserAdded WHERE [Window ID] = #UserID AND
[Rentalman ID] = #RentalManID AND
[BranchAreaNum] = #BranchAreaNum AND
[FirstName] = #FirstName AND
[LastName] = #LastName
// Search for user inside tblUser
SELECT #CheckTblUser = [UserID] FROM dbo.tblUser WHERE [UserID] = #UserID AND
[RentalManID] = #RentalManID
// Add new user if not found in either tblUserAdded or tblUser
// and if the users windows id and rentalmanid are not the same
IF (ISNULL(#CheckTblUserAdded, '') = '') AND (ISNULL(#CheckTblUser, '') = '') AND (#UserID <> #RentalManID)
BEGIN
// Do something here
SELECT #ReturnVal = '1' // as success
END
// Record attempt of adding an existing user
ELSE
BEGIN
// Do something here
SELECT #ReturnVal = '2' // already exists
END
SELECT #ReturnVal
END
This is my raw SQL version of this inside my controller:
public ActionResult Create([Bind(Include="Window_ID,Rentalman_ID,BranchAreaNum,FirstName,LastName")] tblUserAdded tbluseradded)
{
if (ModelState.IsValid)
{
if (tbluseradded.Window_ID == tbluseradded.Rentalman_ID)
{
System.Diagnostics.Debug.WriteLine("The user should already have access since the user's Windows ID and RentalMan ID are the same. Contact IAUnit for help on the issue");
}
else
{
// Rewrite stored procedure in form of raw SQL
string returnVal = "0";
// This will check to see if a user has already been added to tblUser and tblUserAdded
string checkTblUserAdded = db.Database.SqlQuery<string>("SELECT [Window ID] FROM dbo.tblUserAdded WHERE " +
"[Window ID] = '" + tbluseradded.Window_ID + "' AND " +
"[Rentalman ID] = '" + tbluseradded.Rentalman_ID + "' AND " +
"[BranchAreaNum] = '" + tbluseradded.BranchAreaNum + "' AND " +
"[FirstName] = '" + tbluseradded.FirstName + "' AND " +
"[LastName] = '" + tbluseradded.LastName + "'").FirstOrDefault<string>();
string checkTblUser = db.Database.SqlQuery<string>("SELECT [UserID] FROM dbo.tblUser WHERE " +
"[UserID] = '" + tbluseradded.Window_ID + "' AND " +
"[RentalManID] = '" + tbluseradded.Rentalman_ID + "'").FirstOrDefault<string>();
// Add new user if not found in either tblUserAdded or tblUser
if (checkTblUser == null && checkTblUserAdded == null)
{
System.Diagnostics.Debug.WriteLine("We can add the user now! We did it!");
returnVal = "1";
// Do something here
}
else
{
System.Diagnostics.Debug.WriteLine("This user already has access");
returnVal = "2";
// Do something here
}
System.Diagnostics.Debug.WriteLine("The return value is: " + returnVal);
System.Diagnostics.Debug.WriteLine("Please break here!");
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(tbluseradded);
}
I am sure there is a better way of coding this by putting the actual raw SQL in a Data Access Layer, but I am using this for information purposes only.

Get default backup path of sql server programmatically

I am taking backups of certain sql server databases programmatically using c#. I figured that Microsoft.SqlServer.Management.Smo and some other libraries are made for this purpose. Now I can backup a database. Very nice. Here is the code :
var server = new Server(#"" + InstanceName);
var backuper = new Backup();
try
{
backuper.Action = BackupActionType.Database;
backuper.Database = DbName;
backuper.Devices.AddDevice(DbName + ".bak", DeviceType.File);
backuper.BackupSetName = DbName + " - Yedek";
backuper.BackupSetDescription = "Açık Bulut Depo - " + DbName + " - Yedek";
backuper.ExpirationDate = DateTime.Now.AddYears(20);
server.ConnectionContext.Connect();
backuper.SqlBackup(server);
}
catch(Exception ex){//..}
My question here is how can I get the path of the device that the database backed up into?
I know I can specify my own path as :
backuper.Devices.AddDevice("C:\SOMEPATH\" + DbName + ".bak", DeviceType.File);
Then I can actually know where it is, but what I want to do is back it up to its default location and get its path. Please help me out with this.
Correct Answer to this duplicate can be found here: https://stackoverflow.com/a/8791588/331889
Server.BackupDirectory;
Given you are already using SMO Objects it should be the simplest answer.
From this blog post, you could use the function below:
http://www.mssqltips.com/sqlservertip/1966/function-to-return-default-sql-server-backup-folder/
CREATE FUNCTION dbo.fn_SQLServerBackupDir()
RETURNS NVARCHAR(4000)
AS
BEGIN
DECLARE #path NVARCHAR(4000)
EXEC master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
N'Software\Microsoft\MSSQLServer\MSSQLServer',N'BackupDirectory',
#path OUTPUT,
'no_output'
RETURN #path
END;
I normally execute the below stored procedure immediately after backuper.SqlBackup(server);
to return the most recent backup destination path. I use this approach because using SMO, i give the application user the flexibility of backing up to any location / drive or even to a USB disk. So a user may decide not to backup to the default backup location and i want to return that location after the backup process is successfully completed.
USE [YouDatabaseNameHere]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: SQL.NET Warrior
-- =============================================
CREATE PROCEDURE [dbo].[GetBackupHistory]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLVer SQL_VARIANT
,#DBName VARCHAR(128)
,#NumDays SMALLINT
,#SQL VARCHAR(1024)
,#WhereClause VARCHAR(256)
SET #DBName = Null
;
SET #NumDays = 14
;
SET #SQLVer = CONVERT(INTEGER, PARSENAME(CONVERT(VARCHAR(20),SERVERPROPERTY('ProductVersion')),4));
SET #WhereClause = 'WHERE a.type IN (''D'',''I'')
And a.backup_start_date > GETDATE()- ' + CAST(#NumDays AS VARCHAR)+''
IF #DBName IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + '
AND a.database_name = '''+ #DBName +''''
END
SET #SQL = '
SELECT TOP 1 a.database_name,a.backup_start_date
,b.physical_device_name AS BackupPath
,a.position
,a.type
,a.backup_size/1024/1024 AS BackupSizeMB
,' + CASE
WHEN #SQLVer < 10
THEN '0'
ELSE 'a.compressed_backup_size/1024/1024'
END + ' AS CompressedBackMB
FROM msdb.dbo.backupset a
INNER JOIN msdb.dbo.backupmediafamily b
ON a.media_set_id = b.media_set_id
' + #WhereClause + '
ORDER BY a.backup_start_date DESC;';
--PRINT #SQL
EXECUTE (#SQL);
END;
GO

What is the best way to get rows from different databases?

I have 10 identical databases.
I get the database names at runtime.
I want to store rows into a collection of objects.
I also only want one hit on the database server.
My current approach:-
In a query (no stored procedures for X reason) I get list of databases and store in a temporary table.
Then I iterate through each database and create a dynamic query and execute it.
DECLARE #MaxRownum int SET #MaxRownum = (SELECT MAX(RowNum) FROM #Databases)
DECLARE #Iter int SET #Iter = 1
WHILE #Iter <= #MaxRownum
BEGIN
DECLARE #Database varchar(255) SELECT #Database = Databases FROM #Databases
WHERE RowNum = #Iter
IF HAS_DBACCESS(#Database) > 0
BEGIN
//appending query
END
SET #Iter = #Iter + 1
END
EXEC(#Query)
Can I use Linq + entity framework with one hit to server, without dynamic query and without hampering the performance? Is there any better solution?
Having no idea what your query is (I asked but you did not supply it), and not sure that you understand it is going to be extremely difficult to supply database names as variables without a "dynamic query", here is a much simpler way to do it IMHO:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'UNION ALL'
--// you will need to fill in your "//appending query" stuff here:
+ ' SELECT ... FROM ' + QUOTENAME(Databases) + '.dbo.tablename'
FROM #Databases
WHERE HAS_DBACCESS(Databases) = 1;
SET #sql = STUFF(#sql, 1, 9, '');
EXEC sp_executesql #sql;

Categories

Resources