RSS
 

Archive for the ‘SQL Server 2008’ Category

Automated Tests: test your database using SQL Server Snapshots

22 Nov

It makes sense to unit test your database to see if you can do CRUD operations especially when you’re working with LINQ2SQL or Entity Framework. After reading this article by Graeme Hill, we could work with the following methods.

  • Put each test in a transaction
    • Single session, possible deadlocks
  • Rebuild the database after each test
    • Slow
  • Use SQL Server snapshots
    • Looks nice, how to automate this?
  • Distributed Transactions
    • This is the recommended approach. However, our code we want to test is already using Distributed Transactions. Because we will have a transaction A inside another transaction B, we can’t use any foreign key references in transaction C and we’re unable to test all our methods. The transaction B will be committed but as this transaction resides in another transaction A the changes are not reflected to transaction C.

So if we go for the Server snapshot, the rest of this post describes how you can implemented it.

SQL Server snapshot implementation.

There are 3 stored procedures to make this work. You need to create the procedures on a different database than the one you are taking snapshots from (preferably master database).

Creating the snapshot

Needs 4 required parameters.

  • sqlDatabaseName: name of your database
  • sqlSnapShotPath: full path of your snapshot location on your disk (eg. c:\myfolder\)
  • sqlSnapShotName: the name of your snapshot
  • sqlInternalDatabaseName: the internal name of your database
USE [master]
GO
/****** Object:  StoredProcedure [dbo].[Test_CreateSnapShot]    Script Date: 11/21/2010 23:31:19 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[Test_CreateSnapShot]
	-- Add the parameters for the stored procedure here
	@sqlDatabaseName                   varchar(512),
	@sqlSnapShotPath               varchar(512),
	@sqlSnapShotName	               varchar(512),
	@sqlInternalDatabaseName		varchar(512)
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

    DECLARE @sql varchar(500)
  Select @sqlSnapShotPath = @sqlSnapShotPath + @sqlSnapShotName

Select @sql = 'CREATE DATABASE ' + @sqlSnapShotName + ' ON (NAME=' + @sqlInternalDatabaseName + ', FILENAME='''
+ @sqlSnapShotPath + ''') AS SNAPSHOT OF ' + @sqlDatabaseName
print @sql
EXEC(@sql)
END

Closing open connections

The procedure will check the database for any existing connections when replacing the snapshot. If you don’t kill any open connections, you will have the following error.

System.Data.SqlClient.SqlError: RESTORE cannot process database ‘DbName’ because it is in use by this session. It is recommended that the master database be used when performing this operation.

USE [master]
GO
/****** Object:  StoredProcedure [dbo].[Test_ClearDBUsers]    Script Date: 11/21/2010 23:44:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
  CREATE PROCEDURE [dbo].[Test_ClearDBUsers]
    @dbName SYSNAME
AS
BEGIN
    SET NOCOUNT ON 

    DECLARE @spid INT,
        @cnt INT,
        @sql VARCHAR(255) 

    SELECT @spid = MIN(spid), @cnt = COUNT(*)
        FROM master..sysprocesses
        WHERE dbid = DB_ID(@dbname)
        AND spid != @@SPID 

    PRINT 'Starting to KILL '+RTRIM(@cnt)+' processes.' 

    WHILE @spid IS NOT NULL
    BEGIN
        PRINT 'About to KILL '+RTRIM(@spid)
        SET @sql = 'KILL '+RTRIM(@spid)
        EXEC(@sql)
        SELECT @spid = MIN(spid), @cnt = COUNT(*)
            FROM master..sysprocesses
            WHERE dbid = DB_ID(@dbname)
            AND spid != @@SPID
        PRINT RTRIM(@cnt)+' processes remain.'
    END
END

Restoring the snapshot

This procedure uses the ClearDBUsers procedure.
Needs 2 required parameters.

  • sqlDatabaseName: name of your database
  • sqlSnapshotName: the name of your snapshot (same name you gave when creating the snapshot)
USE [master]
GO
/****** Object:  StoredProcedure [dbo].[Test_RestoreSnapShot]    Script Date: 11/21/2010 23:47:12 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[Test_RestoreSnapShot]
	-- Add the parameters for the stored procedure here
	@sqlDatabaseName                   varchar(512),
	@sqlSnapshotName	               varchar(512)
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

	DECLARE @sql varchar(500)

-- make sure we operate on the master and not on the database to restore
-- use master (sp will do this for us)
-- cloase any existing connections
execute Test_ClearDBUsers @sqlDatabaseName

-- restore!
RESTORE DATABASE @sqlDatabaseName
FROM DATABASE_SNAPSHOT = @sqlSnapshotName
Select @sql = 'DROP DATABASE ' + @sqlSnapshotName
exec(@sql)
END

When using Unit Testing, you can call the stored procedures on ClassInitialize (create the snapshot) and on ClassCleanup (remove the snapshot). Creating an ordered Unit Test will make sure we can create our objects first, update (with the right foreign key references) and delete them. How to: Create an ordered test.

        [ClassCleanup()]
        public static void AfterAllTests()
        {
            SqlTestSetup.DeleteSnapShot();
        }

        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            SqlTestSetup.CreateSnapShot();
        }

Calling the stored procedure can be done using your preferred method. This is an example using the Microsoft Data Access Application Blocks.

public static class SqlTestSetup
	{
        private const string spCreateSnapShot = "cip_Test_CreateSnapShot";
        private const string spRestoreSnapShot = "cip_Test_RestoreSnapShot";

        private static string ConnectionString
        {
            get { return ConfigurationManager.ConnectionStrings["CipDatabase"].ConnectionString; }
        }

        public static void CreateSnapShot()
        {
            SqlConnection sqlConnection = new SqlConnection(ConnectionString);

            var paramDatabaseName = new SqlParameter() { ParameterName = "@sqlDatabaseName", SqlValue = SqlDbType.VarChar, Value = sqlConnection.Database.ToString() };
            var paramSnapShotPath = new SqlParameter() { ParameterName = "@sqlSnapShotPath", SqlValue = SqlDbType.VarChar, Value = ConfigurationManager.AppSettings["DBSnapShotPath"].ToString() };
            var paramSqlSnapShotName = new SqlParameter() { ParameterName = "@sqlSnapShotName", SqlValue = SqlDbType.VarChar, Value = ConfigurationManager.AppSettings["DBSnapShotName"].ToString() };
            var paramSqlInternalDatabasName = new SqlParameter() { ParameterName = "@sqlInternalDatabaseName", SqlValue = SqlDbType.VarChar, Value = ConfigurationManager.AppSettings["DBInternalName"].ToString() };

            var parameterValues = new SqlParameter[] { paramDatabaseName, paramSnapShotPath, paramSqlSnapShotName, paramSqlInternalDatabasName };

            // SP should be executed from master db
            string masterConnectionString = ConnectionString.Replace(sqlConnection.Database.ToString(), "master");

            SqlHelper.ExecuteDataset(new SqlConnection(masterConnectionString), CommandType.StoredProcedure,
                                                                     spCreateSnapShot, parameterValues);

        }

        public static void DeleteSnapShot()
        {

            SqlConnection sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["CipDatabase"].ConnectionString);

            var paramDatabaseName = new SqlParameter() { ParameterName = "@sqlDatabaseName", SqlValue = SqlDbType.VarChar, Value = sqlConnection.Database.ToString() };
            var paramSnapShotPath = new SqlParameter() { ParameterName = "@sqlSnapShotName", SqlValue = SqlDbType.VarChar, Value = ConfigurationManager.AppSettings["DBSnapShotName"].ToString() };

            var parameterValues = new SqlParameter[] { paramDatabaseName, paramSnapShotPath };

            // SP should be executed from master db
            string masterConnectionString = ConnectionString.Replace(sqlConnection.Database.ToString(), "master");

            SqlHelper.ExecuteDataset(new SqlConnection(masterConnectionString), CommandType.StoredProcedure,
                                                                 spRestoreSnapShot, parameterValues);
        }
}
 

RoleService not working

05 Feb

The WCF Authentication Service is great to use and it works perfectly… however, I had a lot of problems trying to get the roleservice working.

WCF Role Service is not returning any results. Anything is well configured as described on MSDN: How to: Enable the WCF Authentication Service.

I could call the role service without a problem but there weren’t any queries launched at the database.
After a long search on the internet and msdn, I finally found a small MSDN Note that says the following.

Do not call the GetRolesForCurrentUser method from code that is executing on the Web server. You call the GetRolesForCurrentUser method only as part of a WCF service. For more information about how to read a user’s roles in code that is executing on the Web server, see the GetRolesForUser method.

Why I’m using the roleservice from my ASP.Net application is hard to explain… it’s related to the fact that silverlight, sharepoint login form and custom asp.net websites are using the same webservice. We want to limit the configuration of the Membership and have our forms based user object at WCF level.

So when you’re calling this service from a client application (Silverlight), it will work. If you’re calling this from a server application like ASP.Net, you just don’t get any results back… You should use the Membership classes on your custom WCF Service in order to write your own role methods.

 

Write SQL Database size to text file using stored procedure

10 Jan

I had to write a stored procedure that writes the current database size to a text file on a shared network location. We also need to do this each month, but we’ll use SQL Jobs for that… I didn’t find any good solutions on the net so I will share this one!

Now let’s get to the stored procedure code.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        CODit
-- Create date: 05/01/2010
-- Version: 1.1
-- Description:   Calculate Database Size and write to a file
-- =============================================
CREATE PROCEDURE [dbo].[qry_spaceused_toFile]
 @FullFolderPath varchar(255),
 @DatabaseName varchar(255),
 @SQLServerName varchar(255)
AS
BEGIN
declare @myDate varchar(255),
@QueryString varchar(4000),
@QueryString2 varchar(4000)

Select @myDate = convert(varchar, getdate(), 2)
Select @FullFolderPath = @FullFolderPath + @SQLServerName + '--' + @myDate + '.txt'
Select @QueryString = 'sqlcmd -S"'+@SQLServerName+'" -E -d '+@DatabaseName+' -Q"execute sp_spaceused" >>"'+@FullFolderPath+'" -s"" '
Select @QueryString2 = 'type "'+@FullFolderPath+'"'
create table #errorlog(line varchar(2000))
execute master.dbo.xp_cmdshell @QueryString

insert into #errorlog 

execute master.dbo.xp_cmdshell @QueryString2

Select @QueryString = 'sqlcmd -S"'+@SQLServerName+'" -E -d '+@DatabaseName+' -Q"print CHAR(13)" >>"'+@FullFolderPath+'" -s"" '
execute master.dbo.xp_cmdshell @QueryString
execute master.dbo.xp_cmdshell @QueryString

select line from #errorlog

drop table #errorlog
END

Usage (Make sure your path folder already exists):

exec qry_spaceused_toFile 'PathToFileLocation', 'DataBase', 'SQLServerName'

Possible Output:

database_name database_size unallocated space
————————————————————
CIPPlatform 6.75 MB 0.81 MB

reserved data index_size unused
————————————————————————
3264 KB 1712 KB 1288 KB 264 KB

Explanation:

We are using the sqlcmd to write to a text file. This has some limitations, but it’s fine to use in our situation. I didn’t find any easy method to write to a file using sql. If you want to learn more about the sqlcmd utility, I suggest you to read this tutorial. As the name states, sqlCMD, you normally use this command in a command prompt. However, you can use it in SQL by using the master.dbo.xp_cmdshell procedure!

We are using the built-in system query ‘sp_spaceused’ to calculate the space. After executing, you’ll see two resultsets. In order to write those 2 resultsets to a file we write the results to a temporary table and write the contents that are stored in the table. The output file name is a concatenation of the database name and date.

 

Saving Tables in SQL Server 2008

09 Jun

So you finally set up your SQL Server 2008 and want to try it out by creating some simple tables…If you want to make an Identity of a Primary key, you get following error:

Saving changes is not permitted. The changes you have made require the following table to be dropped and re-created. You have either made changes to a table that can’t be re-created or enabled the option Prevent saving changes that require the table to be re-created.

Saving Table Error Message

Goto to Tools > Options menu. It will bring up wizard for database options. Click on Designers link in left pane. It will bring up following options in right pane. Notice that Prevent saving changes that require table re-creation option is checked. You can uncheck this option to enable these table schema changes.

SLQ Option Dialog

Glad it’s solved, let’s continue!