Cascading deletion with Ado.net - sql

I have an application which i need to delete a row from the table Client
public void Delete_Client(int _id_client)
{
Data.Connect();
using (Data.connexion)
{
string s = "Delete From CLIENT where id_client = " + _id_client;
SqlCommand command = new SqlCommand(s, Data.connexion);
try
{
command.ExecuteNonQuery();
}
catch { }
}
}
the table Client contains a foreign references to another table. So an exception appears indicates that the deletion must be cascade.
So how can i change my code to do this ( i'am using sql server as a dbms) ?

IMO you should avoid using on delete cascade because:
You lose control what is being removed
Table references has to be altered to enable it
Use parametrized query (as all around advice)
So lets change your query. I added ClientOrder as example table which holds foreign key reference to our soon to be deleted client.
First of all I remove all orders linked to client, then I delete client itself. This should go like this for all the other tables
that are linked with Client table.
public void Delete_Client(int _id_client)
{
Data.Connect();
using (Data.connexion)
{
string query = "delete from ClientOrder where id_client = #clientId; delete From CLIENT where id_client = #clientid";
SqlCommand command = new SqlCommand(query, Data.connexion);
command.Parameters.AddWithValue("#clientId", _id_client);
try
{
command.ExecuteNonQuery();
}
catch { } //silencing errors is wrong, if something goes wrong you should handle it
}
}
Parametrized query has many advantages. First of all it is safer (look at SQL Injection attack). Second types are resolved by framework (especially helpful for DateTime with formatting).

Related

Query across two SQLite databases in Delphi TFDQuery [duplicate]

I have an application that uses a SQLite database and everything works the way it should. I'm now in the process of adding new functionalities that require a second SQLite database, but I'm having a hard time figuring out how to join tables from the different databases.
If someone can help me out with this one, I'd really appreciate it!
Edit: See this question for an example case you can adapt to your language when you attach databases as mentioned in the accepted answer.
If ATTACH is activated in your build of Sqlite (it should be in most builds), you can attach another database file to the current connection using the ATTACH keyword. The limit on the number of db's that can be attached is a compile time setting(SQLITE_MAX_ATTACHED), currently defaults to 10, but this too may vary by the build you have. The global limit is 125.
attach 'database1.db' as db1;
attach 'database2.db' as db2;
You can see all connected databases with keyword
.databases
Then you should be able to do the following.
select
*
from
db1.SomeTable a
inner join
db2.SomeTable b on b.SomeColumn = a.SomeColumn;
Note that "[t]he database names main and temp are reserved for the primary database and database to hold temporary tables and other temporary data objects. Both of these database names exist for every database connection and should not be used for attachment".
Here is a C# example to complete this Question
/// <summary>
/// attachSQL = attach 'C:\\WOI\\Daily SQL\\Attak.sqlite' as db1 */
/// path = "Path of the sqlite database file
/// sqlQuery = #"Select A.SNo,A.MsgDate,A.ErrName,B.SNo as BSNo,B.Err as ErrAtB from Table1 as A
/// inner join db1.Labamba as B on
/// A.ErrName = B.Err";
/// </summary>
/// <param name="attachSQL"></param>
/// <param name="sqlQuery"></param>
public static DataTable GetDataTableFrom2DBFiles(string attachSQL, string sqlQuery)
{
try
{
string conArtistName = "data source=" + path + ";";
using (SQLiteConnection singleConnectionFor2DBFiles = new SQLiteConnection(conArtistName))
{
singleConnectionFor2DBFiles.Open();
using (SQLiteCommand AttachCommand = new SQLiteCommand(attachSQL, singleConnectionFor2DBFiles))
{
AttachCommand.ExecuteNonQuery();
using (SQLiteCommand SelectQueryCommand = new SQLiteCommand(sqlQuery, singleConnectionFor2DBFiles))
{
using (DataTable dt = new DataTable())
{
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(SelectQueryCommand))
{
adapter.AcceptChangesDuringFill = true;
adapter.Fill(dt);
return dt;
}
}
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Use Process Exception method An error occurred");
return null;
}
}
Well, I don't have much experience with SQLite you have to access both databases in a single query.
You can have something like :
select name from DB1.table1 as a join DB2.table2 as b where a.age = b.age;
In databases like SQLServer you can access other databases in this hierarchical fashion, this should also work for SQLite.
I think you can initiate an instance of sqlite with more than 1 databases !

Major performance difference between Entity Framework generated sp_executesql and direct query in SSMS

I'm using Entity Framework for making a rather large query. Recently this query is failing due to timeout exceptions.
When I started investigating this issue I used LinqPad and directly copied the SQL output in SSMS and ran the query. This query returns within 1 second!
The query then looks like (only for illustration, the real query is much larger)
DECLARE #p__linq__0 DateTime2 = '2017-10-01 00:00:00.0000000'
DECLARE #p__linq__1 DateTime2 = '2017-10-31 00:00:00.0000000'
SELECT
[Project8].[Volgnummer] AS [Volgnummer],
[Project8].[FkKlant] AS [FkKlant],
-- rest omitted for brevity
Now I used SQL Profiler to capture the real SQL send to the server. The query is exactly the same with the difference that this query is encapsulated within a call to sp_executesql. Like this:
exec sp_executesql N'SELECT
[Project8].[Volgnummer] AS [Volgnummer],
[Project8].[FkKlant] AS [FkKlant],
-- rest omitted for brevity
',N'#p__linq__0 datetime2(7),#p__linq__1 datetime2(7)',
#p__linq__0='2017-10-01 00:00:00',#p__linq__1='2017-10-31 00:00:00'
When I copy/paste this query in SSMS it runs for 60 seconds and thus results in a timeout when using from EF with default settings!
I can't wrap my head around why this difference is occurring, as this is the same query, the only thing is, it is executed differently.
I read a lot about why EF uses sp_executesql and I understand why. I also read that sp_executesql is different from EXEC because it makes use of the queryplan cache, but I don't understand why the SQL optimizer has such difficulty in creating a performant query plan for the sp_executesql version whereas it is capable of creating a performant queryplan for the direct query version.
I'm not sure if the complete query itself adds to the question. If it does, let me know and I will make an edit.
Thanks to the supplied comments I managed two things:
I now understand the query plan and the differences between parameter sniffing and variables in queries
I implemented a DbCommandInterceptor to add OPTION (OPTIMIZE FOR UNKNOWN) to the query when needed.
The SQL query compiled by Entity Framework can be intercepted before send to the server by adding an implementation to DbInterception.
Such an implementation is trivial to make:
public class QueryHintInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
queryHint = " OPTION (OPTIMIZE FOR UNKNOWN)";
if (!command.CommandText.EndsWith(queryHint))
{
command.CommandText += queryHint;
}
base.ReaderExecuting(command, interceptionContext);
}
}
// Add to the interception proces:
DbInterception.Add(new QueryHintsInterceptor());
As Entity Framework also caches the queries, I check if an optimization already has been added.
But this approach will intercept all queries and obviously one should not do this. As the DbCommandInterceptionContext gives access to the DbContext I added an interface with a single property (ISupportQueryHints) to my DbContext which I set to a optimization when the query needs this.
This now looks like this:
public class QueryHintInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
var dbContext =
interceptionContext.DbContexts.FirstOrDefault(d => d is ISupportQueryHints) as ISupportQueryHints;
if (dbContext != null)
{
var queryHint = $" OPTION ({dbContext.QueryHint})";
if (!command.CommandText.EndsWith(queryHint))
{
command.CommandText += queryHint;
}
}
base.ReaderExecuting(command, interceptionContext);
}
}
Where needed this can be used as:
public IEnumerable<SomeDto> QuerySomeDto()
{
using (var dbContext = new MyQuerySupportingDbContext())
{
dbContext.QueryHint = "OPTIMIZE FOR UNKNOWN";
return this.PerformQuery(dbContext);
}
}
Because my application makes use of a message based architecture surrounding commands and queries as described here my implementation consists of a decorator around the queryhandlers in need of optimization. This decorator sets the query hints to the DbContext whenever needed. This is however an implementation detail. The basic idea stays the same.
I updated #Ric.Net's QueryHintInterceptor class to handle the case where multiple contexts are being used for a query and may have their own hints:
public class QueryHintInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
var contextHints = interceptionContext.DbContexts
.Select(c => (c as ISupportQueryHints)?.QueryHint)
.Where(h => !string.IsNullOrEmpty(h))
.Distinct()
.ToList();
var queryHint = $"{System.Environment.NewLine}OPTION ({ string.Join(", ", contextHints) })";
if (contextHints.Any() && !command.CommandText.EndsWith(queryHint))
{
command.CommandText += queryHint;
}
base.ReaderExecuting(command, interceptionContext);
}
}
Although honestly, if you're at that point, you might consider building a more robust solution like the one described here.

SQL Server connection context using temporary table cannot be used in stored procedures called with SqlDataAdapter.Fill

I want to have some information available for any stored procedure, such as current user. Following the temporary table method indicated here, I have tried the following:
1) create temporary table when connection is opened
private void setConnectionContextInfo(SqlConnection connection)
{
if (!AllowInsertConnectionContextInfo)
return;
var username = HttpContext.Current?.User?.Identity?.Name ?? "";
var commandBuilder = new StringBuilder($#"
CREATE TABLE #ConnectionContextInfo(
AttributeName VARCHAR(64) PRIMARY KEY,
AttributeValue VARCHAR(1024)
);
INSERT INTO #ConnectionContextInfo VALUES('Username', #Username);
");
using (var command = connection.CreateCommand())
{
command.Parameters.AddWithValue("Username", username);
command.ExecuteNonQuery();
}
}
/// <summary>
/// checks if current connection exists / is closed and creates / opens it if necessary
/// also takes care of the special authentication required by V3 by building a windows impersonation context
/// </summary>
public override void EnsureConnection()
{
try
{
lock (connectionLock)
{
if (Connection == null)
{
Connection = new SqlConnection(ConnectionString);
Connection.Open();
setConnectionContextInfo(Connection);
}
if (Connection.State == ConnectionState.Closed)
{
Connection.Open();
setConnectionContextInfo(Connection);
}
}
}
catch (Exception ex)
{
if (Connection != null && Connection.State != ConnectionState.Open)
Connection.Close();
throw new ApplicationException("Could not open SQL Server Connection.", ex);
}
}
2) Tested with a procedure which is used to populate a DataTable using SqlDataAdapter.Fill, by using the following function:
public DataTable GetDataTable(String proc, Dictionary<String, object> parameters, CommandType commandType)
{
EnsureConnection();
using (var command = Connection.CreateCommand())
{
if (Transaction != null)
command.Transaction = Transaction;
SqlDataAdapter adapter = new SqlDataAdapter(proc, Connection);
adapter.SelectCommand.CommandTimeout = CommonConstants.DataAccess.DefaultCommandTimeout;
adapter.SelectCommand.CommandType = commandType;
if (Transaction != null)
adapter.SelectCommand.Transaction = Transaction;
ConstructCommandParameters(adapter.SelectCommand, parameters);
DataTable dt = new DataTable();
try
{
adapter.Fill(dt);
return dt;
}
catch (SqlException ex)
{
var err = String.Format("Error executing stored procedure '{0}' - {1}.", proc, ex.Message);
throw new TptDataAccessException(err, ex);
}
}
}
3) called procedure tries to get the username like this:
DECLARE #username VARCHAR(128) = (select AttributeValue FROM #ConnectionContextInfo where AttributeName = 'Username')
but #ConnectionContextInfo is no longer available in the context.
I have put a SQL profiler against the database, to check what is happening:
temporary table is created successfully using a certain SPID
procedure is called using the same SPID
Why is temporary table not available within the procedure scope?
In T-SQL doing the following works:
create a temporary table
call a procedure that needs data from that particular temporary table
temporary table is dropped only explicitly or after current scope ends
Thanks.
As was shown in this answer, ExecuteNonQuery uses sp_executesql when CommandType is CommandType.Text and command has parameters.
The C# code in this question doesn't set the CommandType explicitly and it is Text by default, so most likely end result of the code is that CREATE TABLE #ConnectionContextInfo is wrapped into sp_executesql. You can verify it in the SQL Profiler.
It is well-known that sp_executesql is running in its own scope (essentially it is a nested stored procedure). Search for "sp_executesql temp table". Here is one example: Execute sp_executeSql for select...into #table but Can't Select out Temp Table Data
So, a temp table #ConnectionContextInfo is created in the nested scope of sp_executesql and is automatically deleted as soon as sp_executesql returns.
The following query that is run by adapter.Fill doesn't see this temp table.
What to do?
Make sure that CREATE TABLE #ConnectionContextInfo statement is not wrapped into sp_executesql.
In your case you can try to split a single batch that contains both CREATE TABLE #ConnectionContextInfo and INSERT INTO #ConnectionContextInfo into two batches. The first batch/query would contain only CREATE TABLE statement without any parameters. The second batch/query would contain INSERT INTO statement with parameter(s).
I'm not sure it would help, but worth a try.
If that doesn't work you can build one T-SQL batch that creates a temp table, inserts data into it and calls your stored procedure. All in one SqlCommand, all in one batch. This whole SQL will be wrapped in sp_executesql, but it would not matter, because the scope in which temp table is created will be the same scope in which stored procedure is called. Technically it will work, but I wouldn't recommend following this path.
Here is not an answer to the question, but suggestion to solve the problem.
To be honest, the whole approach looks strange. If you want to pass some data into the stored procedure why not use parameters of this stored procedure. This is what they are for - to pass data into the procedure. There is no real need to use temp table for that. You can use a table-valued parameter (T-SQL, .NET) if the data that you are passing is complex. It is definitely an overkill if it is simply a Username.
Your stored procedure needs to be aware of the temp table, it needs to know its name and structure, so I don't understand what's the problem with having an explicit table-valued parameter instead. Even the code of your procedure would not change much. You'd use #ConnectionContextInfo instead of #ConnectionContextInfo.
Using temp tables for what you described makes sense to me only if you are using SQL Server 2005 or earlier, which doesn't have table-valued parameters. They were added in SQL Server 2008.
MINOR ISSUE: I am going to assume for the moment that the code posted in the Question isn't the full piece of code that is running. Not only are there variables used that we don't see getting declared (e.g. AllowInsertConnectionContextInfo), but there is a glaring omission in the setConnectionContextInfo method: the command object is created but never is its CommandText property set to commandBuilder.ToString(), hence it appears to be an empty SQL batch. I'm sure that this is actually being handled correctly since 1) I believe submitting an empty batch will generate an exception, and 2) the question does mention that the temp table creation appears in the SQL Profiler output. Still, I am pointing this out as it implies that there could be additional code that is relevant to the observed behavior that is not shown in the question, making it more difficult to give a precise answer.
THAT BEING SAID, as mentioned in #Vladimir's fine answer, due to the query running in a sub-process (i.e. sp_executesql), local temporary objects -- tables and stored procedures -- do not survive the completion of that sub-process and hence are not available in the parent context.
Global temporary objects and permanent/non-temporary objects will survive the completion of the sub-process, but both of those options, in their typical usage, introduce concurrency issues: you would need to test for the existence first before attempting to create the table, and you would need a way to distinguish one process from another. So these are not really a great option, at least not in their typical usage (more on that later).
Assuming that you cannot pass in any values into the Stored Procedure (else you could simply pass in the username as #Vladimir suggested in his answer), you have a few options:
The easiest solution, given the current code, would be to separate the creation of the local temporary table from the INSERT command (also mentioned in #Vladimir's answer). As previously mentioned, the issue you are encountering is due to the query running within sp_executesql. And the reason sp_executesql is being used is to handle the parameter #Username. So, the fix could be as simple as changing the current code to be the following:
string _Command = #"
CREATE TABLE #ConnectionContextInfo(
AttributeName VARCHAR(64) PRIMARY KEY,
AttributeValue VARCHAR(1024)
);";
using (var command = connection.CreateCommand())
{
command.CommandText = _Command;
command.ExecuteNonQuery();
}
_Command = #"
INSERT INTO #ConnectionContextInfo VALUES ('Username', #Username);
");
using (var command = connection.CreateCommand())
{
command.CommandText = _Command;
// do not use AddWithValue()!
SqlParameter _UserName = new SqlParameter("#Username", SqlDbType.NVarChar, 128);
_UserName.Value = username;
command.Parameters.Add(_UserName);
command.ExecuteNonQuery();
}
Please note that temporary objects -- local and global -- cannot be accessed in T-SQL User-Defined Functions or Table-Valued Functions.
A better solution (most likely) would be to use CONTEXT_INFO, which is essentially session memory. It is a VARBINARY(128) value but changes to it survive any sub-process since it is not an object. Not only does this remove the current complication you are facing, but it also reduces tempdb I/O considering that you are creating and dropping a temporary table each time this process runs, and doing an INSERT, and all 3 of those operations are written to disk twice: first in the Transaction Log, then in the data file. You can use this in the following manner:
string _Command = #"
DECLARE #User VARBINARY(128) = CONVERT(VARBINARY(128), #Username);
SET CONTEXT_INFO #User;
";
using (var command = connection.CreateCommand())
{
command.CommandText = _Command;
// do not use AddWithValue()!
SqlParameter _UserName = new SqlParameter("#Username", SqlDbType.NVarChar, 128);
_UserName.Value = username;
command.Parameters.Add(_UserName);
command.ExecuteNonQuery();
}
And then you get the value within the Stored Procedure / User-Defined Function / Table-Valued Function / Trigger via:
DECLARE #Username NVARCHAR(128) = CONVERT(NVARCHAR(128), CONTEXT_INFO());
That works just fine for a single value, but if you need multiple values, or if you are already using CONTEXT_INFO for another purpose, then you either need to go back to one of the other methods described here, OR, if using SQL Server 2016 (or newer), you can use SESSION_CONTEXT, which is similar to CONTEXT_INFO but is a HashTable / Key-Value pairs.
Another benefit of this approach is that CONTEXT_INFO (at least, I haven't yet tried SESSION_CONTEXT) is available in T-SQL User-Defined Functions and Table-Valued Functions.
Finally, another option would be to create a global temporary table. As mentioned above, global objects have the benefit of surviving sub-processes, but they also have the drawback of complicating concurrency. A seldom-used to get the benefit without the drawback is to give the temporary object a unique, session-based name, rather than add a column to hold a unique, session-based value. Using a name that is unique to the session removes any concurrency issues while allowing you to use an object that will get automatically cleaned up when the connection is closed (so no need to worry about a process that creates a global temporary table and then runs into an error before completing, whereas using a permanent table would require cleanup, or at least an existence check at the beginning).
Keeping in mind the restriction that we cannot pass any value into the Stored Procedure, we need to use a value that already exists at the data layer. The value to use would be the session_id / SPID. Of course, this value does not exist in the app layer, so it has to be retreived, but there was no restriction placed on going in that direction.
int _SessionId;
using (var command = connection.CreateCommand())
{
command.CommandText = #"SET #SessionID = ##SPID;";
SqlParameter _paramSessionID = new SqlParameter("#SessionID", SqlDbType.Int);
_paramSessionID.Direction = ParameterDirection.Output;
command.Parameters.Add(_UserName);
command.ExecuteNonQuery();
_SessionId = (int)_paramSessionID.Value;
}
string _Command = String.Format(#"
CREATE TABLE ##ConnectionContextInfo_{0}(
AttributeName VARCHAR(64) PRIMARY KEY,
AttributeValue VARCHAR(1024)
);
INSERT INTO ##ConnectionContextInfo_{0} VALUES('Username', #Username);", _SessionId);
using (var command = connection.CreateCommand())
{
command.CommandText = _Command;
SqlParameter _UserName = new SqlParameter("#Username", SqlDbType.NVarChar, 128);
_UserName.Value = username;
command.Parameters.Add(_UserName);
command.ExecuteNonQuery();
}
And then you get the value within the Stored Procedure / Trigger via:
DECLARE #Username NVARCHAR(128),
#UsernameQuery NVARCHAR(4000);
SET #UsernameQuery = CONCAT(N'SELECT #tmpUserName = [AttributeValue]
FROM ##ConnectionContextInfo_', ##SPID, N' WHERE [AttributeName] = ''Username'';');
EXEC sp_executesql
#UsernameQuery,
N'#tmpUserName NVARCHAR(128) OUTPUT',
#Username OUTPUT;
Please note that temporary objects -- local and global -- cannot be accessed in T-SQL User-Defined Functions or Table-Valued Functions.
Finally, it is possible to use a real / permanent (i.e. non-temporary) Table, provided that you include a column to hold a value specific to the current session. This additional column will allow for concurrent operations to work properly.
You can create the table in tempdb (yes, you can use tempdb as a regular DB, doesn't need to be only temporary objects starting with # or ##). The advantages of using tempdb is that the table is out of the way of everything else (it is just temporary values, after all, and doesn't need to be restored, so tempdb using SIMPLE recovery model is perfect), and it gets cleaned up when the Instance restarts (FYI: tempdb is created brand new as a copy of model each time SQL Server starts).
Just like with Option #3 above, we can again use the session_id / SPID value since it is common to all operations on this Connection (as long as the Connection remains open). But, unlike Option #3, the app code doesn't need the SPID value: it can be inserted automatically into each row using a Default Constraint. This simplies the operation a little.
The concept here is to first check to see if the permanent table in tempdb exists. If it does, then make sure that there is no entry already in the table for the current SPID. If it doesn't, then create the table. Since it is a permanent table, it will continue to exist, even after the current process closes its Connection. Finally, insert the #Username parameter, and the SPID value will populate automatically.
// assume _Connection is already open
using (SqlCommand _Command = _Connection.CreateCommand())
{
_Command.CommandText = #"
IF (OBJECT_ID(N'tempdb.dbo.Usernames') IS NOT NULL)
BEGIN
IF (EXISTS(SELECT *
FROM [tempdb].[dbo].[Usernames]
WHERE [SessionID] = ##SPID
))
BEGIN
DELETE FROM [tempdb].[dbo].[Usernames]
WHERE [SessionID] = ##SPID;
END;
END;
ELSE
BEGIN
CREATE TABLE [tempdb].[dbo].[Usernames]
(
[SessionID] INT NOT NULL
CONSTRAINT [PK_Usernames] PRIMARY KEY
CONSTRAINT [DF_Usernames_SessionID] DEFAULT (##SPID),
[Username] NVARCHAR(128) NULL,
[InsertTime] DATETIME NOT NULL
CONSTRAINT [DF_Usernames_InsertTime] DEFAULT (GETDATE())
);
END;
INSERT INTO [tempdb].[dbo].[Usernames] ([Username]) VALUES (#UserName);
";
SqlParameter _UserName = new SqlParameter("#Username", SqlDbType.NVarChar, 128);
_UserName.Value = username;
command.Parameters.Add(_UserName);
_Command.ExecuteNonQuery();
}
And then you get the value within the Stored Procedure / User-Defined Function / Table-Valued Function / Trigger via:
SELECT [Username]
FROM [tempdb].[dbo].[Usernames]
WHERE [SessionID] = ##SPID;
Another benefit of this approach is that permanent tables are accessible in T-SQL User-Defined Functions and Table-Valued Functions.
"There are two types of temporary tables: local and global. They differ from each other in their names, their visibility, and their availability. Local temporary tables have a single number sign (#) as the first character of their names; they are visible only to the current connection for the user, and they are deleted when the user disconnects from the instance of SQL Server. Global temporary tables have two number signs (##) as the first characters of their names; they are visible to any user after they are created, and they are deleted when all users referencing the table disconnect from the instance of SQL Server." -- from here
so the answer to your problem is put ## instead of # to make the local temporary table to global.

NHibernate CreateSQLQuery treats entities updated

i am trying to retrieve data from database via nhibernate's CreateSQLQuery on a stored proceduure. Something like the following code.
then I am basically doing a session transaction commit, however the commit throws an "cannot update" exception. It is trying to execute a update statement on CustomEntityDao.
const string selectSQL = "EXEC GetDataSP #Id = :Id";
var query = Session.CreateSQLQuery(selectSQL);
query.SetString("Id", "10");
query.AddEntity(typeof (CustomEntityDao));
var entityList = query.List<CustomEntityDao>();
try
{
Session.Transaction.Commit();
}
catch (Exception ex)
{
throw ex;
}
My question is why are the entities been treated as modified, as you can see in the code I am only doing a query.
May be there is something else going on your code, what I can suggest is use NHibernate Profiler a trial version of which can be downloaded from www.nhprof.com and monitor the SQL commands that are being fired and notice what objects are being retrieved.
Also I dont understand why are you committing the transaction in the first place.
To solve this particular problem you could always use NHibernates StatelessSession which doesnt track entities or you can also use Session.Evict and ask Nhibernate to stop tracking particular objects.

Database in use error with Entity Framework 4 Code First

I have an MVC3 and EF 4 Code First application, which is configured to change the DB when the model changes, by setting the DB Initializer to a DropCreateDatabaseIfModelChanges<TocratesDb>, where TocratesDb is my derived DbContext.
I have now made a change to the model, by adding properties to a class, but when EF tries to drop and recreate the DB, I get the following error:
Cannot drop database "Tocrates" because it is currently in use.
I have absolutely no other connections anywhere open on this database. I assume that my cDbContext still has an open connection to the database, but what can I do about this?
NEW: Now my problem is how to re-create the database based on the model. By using the more general IDatabaseInitializer, I lose that and have to implement it myself.
Your current context must have an opened connection to be able to drop the database. The problem is that there can be other opened connections which will block your db initializer. One very nice example is having opened any table from your database in management studio. Another possible problem can be opened connections in the connection pool of your application.
In MS SQL this can be avoided for example by switching DB to SINGLE USER mode and forcing all connections to be closed and incomplete transactions rolled back:
ALTER DATABASE Tocrates SET SINGLE_USER WITH ROLLBACK IMMEDIATE
You can create a new intializer which will first call this command and then drops the database. Be aware that you should handle a database connection by yourselves because ALTER DATABASE and DROP DATABASE must be called on the same connection.
Edit:
Here you have example using Decorator pattern. You can modify it and initialize inner initializer inside the constructor instead of passing it as a parameter.
public class ForceDeleteInitializer : IDatabaseInitializer<Context>
{
private readonly IDatabaseInitializer<Context> _initializer;
public ForceDeleteInitializer(IDatabaseInitializer<Context> innerInitializer)
{
_initializer = innerInitializer;
}
public void InitializeDatabase(Context context)
{
context.Database.SqlCommand("ALTER DATABASE Tocrates SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
_initializer.InitializeDatabase(context);
}
}
I found in EF 6 this fails with an ALTER DATABASE statement not allowed within multi-statement transaction error.
The solution was to use the new transaction behavior overload like this:
context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, "ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
I had the same issue.
I resolved it by closing a connection open under the Server Explorer view of Visual Studio.
I realize this is dated but I couldn't get the accepted solution working so I rolled a quick solution...
using System;
using System.Data.Entity;
namespace YourCompany.EntityFramework
{
public class DropDatabaseInitializer<T> : IDatabaseInitializer<T> where T : DbContext, new()
{
public DropDatabaseInitializer(Action<T> seed = null)
{
Seed = seed ?? delegate {};
}
public Action<T> Seed { get; set; }
public void InitializeDatabase(T context)
{
if (context.Database.Exists())
{
context.Database.ExecuteSqlCommand("ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
context.Database.ExecuteSqlCommand("USE master DROP DATABASE [" + context.Database.Connection.Database + "]");
}
context.Database.Create();
Seed(context);
}
}
}
This works for me and supports seeding easily.
In Visual Studio 2012, the SQL Server Object Explorer window can hold a connection to the database. Closing the window and all windows opened from it releases the connection.
A simple closing of my whole project and reopening it did the trick for me. It's the easiest way to make sure there are no connections still open