I had the connection string and a bunch of my unit tests using it in order to test the logic of some class which was applying some CRUD operations to it. So I was passing it as a private constant field in test class and sharing it to my tests. Everything worked perfectly fine!
But then I realized I have to do it as integration testing. So I've decided to use static helper class to create database via session for me tests to work with it and then drop.
The class is the following:
public static class LocalDB
{
public const string DB_DIRECTORY = "Data";
public static string GetLocalDB(string dbName, bool deleteIfExists = false)
{
try
{
var outputFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), DB_DIRECTORY);
var mdfFilename = dbName + ".mdf";
var dbFileName = Path.Combine(outputFolder, mdfFilename);
var logFileName = Path.Combine(outputFolder, $"{dbName}_log.ldf");
if (!Directory.Exists(outputFolder))
{
Directory.CreateDirectory(outputFolder);
}
if (File.Exists(dbFileName) && deleteIfExists)
{
if (File.Exists(logFileName)) File.Delete(logFileName);
File.Delete(dbFileName);
CreateDatabase(dbName, dbFileName);
}
else if (!File.Exists(dbFileName))
{
CreateDatabase(dbName, dbFileName);
}
var connectionString = string.Format(#"Data Source=(LocalDB)\v11.0;AttachDBFileName={1};Initial Catalog={0};Integrated Security=True;", dbName, dbFileName);
CreateTable(connectionString, "Cpa", dbName);
return connectionString;
}
catch(Exception ex)
{
throw;
}
}
public static bool CreateDatabase(string dbName, string dbFileName)
{
try
{
var connectionString = #"Data Source=(LocalDB)\v11.0;Initial Catalog=master;Integrated Security=True";
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var cmd = connection.CreateCommand();
DetachDatabase(dbName);
cmd.CommandText = string.Format("CREATE DATABASE {0} ON (NAME = N'{0}', FILENAME = '{1}')", dbName, dbFileName);
cmd.ExecuteNonQuery();
cmd.Dispose();
}
return File.Exists(dbFileName);
}
catch
{
throw;
}
}
public static bool DetachDatabase(string dbName)
{
try
{
var connectionString = $#"Data Source=(LocalDB)\v11.0;Initial Catalog=master;Integrated Security=True";
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = $"exec sp_detach_db '{dbName}'";
cmd.ExecuteNonQuery();
cmd.Dispose();
return true;
}
}
catch(Exception ex)
{
return false;
}
}
public static bool CreateTable(string connectionString, string tableName, string dbName)
{
connectionString = connectionString.Replace("master", dbName);
try
{
using (var connection = new SqlConnection(connectionString))
{
var createTableQuery = $#"CREATE TABLE {tableName}(
CrmId nvarchar(50) NOT NULL,
Service nvarchar(25) NOT NULL,
RecurringReference nvarchar(50),
ExpiryDate datetime,
CardNumber nvarchar(50),
Enabled bit,
Brand nvarchar(50),
CpaType nvarchar(50),
Channel nvarchar(50)
);";
var command = new SqlCommand(createTableQuery, connection);
connection.Open();
var reader = command.ExecuteReader();
reader.Dispose();
return true;
}
}
catch (Exception ex)
{
return false;
}
}
}
I was calling it's GetLocalDB method in my test class' ctor amd initializing field.
After that I've got the following error "the process cannot access the file blah log.ldf because it is used by another process"
!!! All the tests were using the same connection string, I have no idea what's gone wrong. It can't be the unit tests failure for all I've changed is connection string (was for already existent db -> changed to temporary local (LocalDb class))
Thanks!
I also used localDb for testing in my web application and had a similar issue. This issue can sometime happen if while debugging you stopped in between or some test ran into any exception and disposing of localdb did not happen properly. If that happens then next time when you start running test it wont create the db(inspite of the check ifdbexists) and run into some exception like you mentioned. To check I added a different name every time we ran a set of tests so that db gets created on start of running test and drops after executing all the tests.As its not ideal to give different name every time , try disposing the localdb in a finally block to make sure it happens even in case of exception
Related
Im a beginner to C# (.net of course) and for my final year project im developing a payroll system. Now I have some issues regarding ado.net sql connection object.
To keep the connection string centrally I have used a separate class call db. Taking another step to this centralization thinking, I've initialized the connection object also centrally in this db class as follows.
class db
{
string connectionString = ("connection string will be here...");
public SqlConnection GetConn()
{
SqlConnection NewConn = new SqlConnection(connectionString);
return NewConn;
}
}
Now Im using this connection object as follows in my application...
I just want to know whether I would face issues in future because of this practice and also appreciate if one of experts could explain me what is the best practice in this regard.
Thanks in advance
class client
{
db NewDB = new db(); // db class is instantiated...
SqlConnection newCon; // object referece newConn is created...
//Method to insert new clients to 'client' table
public void addNewClient(DateTime entDate, client NewClient)
{
try
{
newCon = NewDB.GetConn(); // connection object is assigned to newCon... but this is optional and I put this for the clarity
string CommandString = "INSERT INTO client(Client_Name, C_Add, Contact_Person, C_Mob_No, C_Tel_No, Remarks, Ent_Date)" +
" VALUES (#CName, #CAdd, #CPerson, #CMob, #CTel, #Remarks, #entDate)";
SqlCommand SqlCom = new SqlCommand();
SqlCom.CommandText = CommandString;
SqlCom.Parameters.Add("#CName", SqlDbType.VarChar).Value = NewClient.CName;
SqlCom.Parameters.Add("#CAdd", SqlDbType.VarChar).Value = NewClient.CAdd;
SqlCom.Parameters.Add("#CPerson", SqlDbType.VarChar).Value = NewClient.CPerson;
SqlCom.Parameters.Add("#CMob", SqlDbType.Char).Value = NewClient.CMob;
SqlCom.Parameters.Add("#CTel", SqlDbType.Char).Value = NewClient.CTel;
SqlCom.Parameters.Add("#Remarks", SqlDbType.VarChar).Value = NewClient.Remarks;
SqlCom.Parameters.Add("#entDate", SqlDbType.Date).Value = entDate;
SqlCom.Connection = newCon;
newCon.Open();
SqlCom.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
newCon.Close(); // newCon object is global to entire class so can call its close method.
}
}
}
You don't need to use global connection object. Your db connections are stored in connection pool. So you won't run out of connections. Read more about connections pooling.
Looking at your client class it is bad practice to write raw SQl in the code. It is better practice to write a stored procedure and call it from the code passing the parameters.
public void addNewClient(DateTime entDate, client NewClient)
{
try
{
newCon = NewDB.GetConn(); // create connection
conn.Open(); //open connection
// create a command object identifying the stored procedure
SqlCommand SqlCom = new SqlCommand("Store Procedure name", newConn);
// add parameter to sql command, which is passed to the stored procedure
SqlCom .Parameters.Add(new SqlParameter("#CName", NewClient.CName));
// Rest of parameters
// execute the command
cmd.ExecuteReader();
}
}
Creating a class to store the connection is good practice. However you could expand on this further.
public struct slqParameters
{
public object ParamData { get; set; }
public string ParamKey { get; set; }
public SqlDbType ParamDatabaseType { get; set; }
public int ParamSize { get; set; }
}
class db
{
private string connectionString = ("connection string will be here...");
public static void ExecuteStoreProcedure(string ProcedureName, ref slqParameters[] CommandParameters)
{
string str_ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
try
{
using (SqlConnection sqlConnection = new SqlConnection(str_ConnectionString))
{
using (SqlCommand sqlCommand = new SqlCommand(sProcedureName, sqlConnection) { CommandType = CommandType.StoredProcedure })
{
// Add all the parameters to the sql command.
foreach (slqParametersParameter in CommandParameters)
{
// Add a parameter
sqlCommand.Parameters.Add(new SqlParameter(Parameter.ParamKey, Parameter._ParamDatabaseType , Parameter._ParamSize ) { Value = Parameter.ParamData });
}
sqlConnection.Open();
DataTable dtTable = new DataTable();
sqlCommand.ExecuteReader())
}
}
}
catch (Exception error)
{
throw error;
}
}
}
This is only a ruff guide as i have not tested it yet but it should work
To use it on your page
Public SomeMethod()
{
slqParameters[] parameters = new Parameters[1]
{
new sqlParameters{ ParamData = , paramKey = "#CName", ParamDatabaseType = NewClient.CName}
};
db.ExecuteStoreProcedure("Store Procedure name", parameters);
}
I've been trying to get my query to work for some time it runs but doesn't insert anything nor does it return any errors.
The database connection is open and is successfuly connection.
The Table is called errorlog and holds the following data
- id (int autoincremental, Primary key, Unique)
- exception (varchar)
- time (DateTime)
exception = String(error message)
time = DateTime.Now
Here's the code:
public void insertError(string error, DateTime time)
{
SqlCeParameter[] sqlParams = new SqlCeParameter[]
{
new SqlCeParameter("#exception", error),
new SqlCeParameter("#time", time)
};
try
{
cmd = new SqlCeCommand();
cmd.Connection = connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO errorlog (exception, time) VALUES(#exception, #time)";
cmd.Parameters.AddRange(sqlParams);
cmd.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
Any help would be appreciated, Thanks in advance.
EDIT
Removed quotes around #exception
Heres the connection:
protected DataController()
{
try
{
string appPath = System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(DataController)).CodeBase).Replace(#"file:\", "") + #"\";
string strCon = #"Data Source = " + appPath + #"Data\EasyShop.sdf";
connection = new SqlCeConnection(strCon);
}
catch (Exception e)
{
}
connection.Open();
}
Finally the way it gets called:
public bool log(string msg, bool timestamp = true)
{
DataController dc = DataController.Instance();
dc.insertError(msg, DateTime.Today);
return true;
}
Debug your application and see if connection points exactly to the
database you want. Also check if you look for the inserted records
in the same database.
If your connection belongs to the transaction, check if it's committed. You will not see those records inserted until transaction is committed.
It seems to me, that you INSERT is wrong. Remove quotes around #exception
Open SQL Server Profiler, connect to your database and check if your INSERT appears in there.
I originally posted the question to check for presence of either ADO.NET/OLEDB connection types. That being resolved, I'd like to know, how to change the code when it comes to inserting to the DB.
For example, when the connection type is ADO.NET we use the "Transaction" in the connection type.
SqlConnection connection = (SqlConnection)connections[_connectionName].AcquireConnection(transaction);
Now if I have the OLEDB connection (instead of ADO.NET), I'd like to handle that situation in this code. What do I need to do. Sorry if I dont sound technical enough, but I am not a C# person. Thanks again for your kind help.
EDIT
I am pasting the whole code here because I cant seem to use OLEDB type connections. I can only use ADO.NET...I know its something simple, but I dont know what it is.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Dts.Runtime;
using System.Data.OleDb;
using System.Data.Common;
namespace AOC.SqlServer.Dts.Tasks
{
[DtsTask(
DisplayName = "Custom Logging Task",
Description = "Writes logging info into a table")]
public class CustomLoggingTask : Task
{
private string _packageName;
private string _taskName;
private string _errorCode;
private string _errorDescription;
private string _machineName;
private double _packageDuration;
private string _connectionName;
private string _eventType;
private string _executionid;
private DateTime _handlerdatetime;
public string ConnectionName
{
set
{
_connectionName = value;
}
get
{
return _connectionName;
}
}
public string Event
{
set
{
_eventType = value;
}
get
{
return _eventType;
}
}
public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log)
{
const string METHOD_NAME = "CustomLoggingTask-Validate";
try
{
if (string.IsNullOrEmpty(_eventType))
{
componentEvents.FireError(0, METHOD_NAME, "The event property must be specified", "", -1);
return DTSExecResult.Failure;
}
if (string.IsNullOrEmpty(_connectionName))
{
componentEvents.FireError(0, METHOD_NAME, "No connection has been specified", "", -1);
return DTSExecResult.Failure;
}
DbConnection connection = connections[_connectionName].AcquireConnection(null) as DbConnection;
if (connection == null)
{
componentEvents.FireError(0, METHOD_NAME, "The connection is not a valid ADO.NET connection", "", -1);
return DTSExecResult.Failure;
}
if (!variableDispenser.Contains("System::SourceID"))
{
componentEvents.FireError(0, METHOD_NAME, "No System::SourceID variable available. This task can only be used in an Event Handler", "", -1);
return DTSExecResult.Failure;
}
return DTSExecResult.Success;
}
catch (Exception exc)
{
componentEvents.FireError(0, METHOD_NAME, "Validation Failed: " + exc.ToString(), "", -1);
return DTSExecResult.Failure;
}
}
public override DTSExecResult Execute(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log, object transaction)
{
try
{
string commandText =
#"INSERT INTO SSISLog (EventType, PackageName, TaskName, EventCode, EventDescription, PackageDuration, Host, ExecutionID, EventHandlerDateTime)
VALUES (#EventType, #PackageName, #TaskName, #EventCode, #EventDescription, #PackageDuration, #Host, #Executionid, #handlerdatetime)";
ReadVariables(variableDispenser);
DbConnection connection = connections[_connectionName].AcquireConnection(transaction) as DbConnection;
//SqlConnection connection = (SqlConnection)connections[_connectionName].AcquireConnection(transaction);
DbCommand command = null;
//using (SqlCommand command = new SqlCommand())
if (connection is SqlConnection)
command = new SqlCommand();
else if (connection is OleDbConnection)
command = new OleDbCommand();
{
command.CommandText = commandText;
command.CommandType = CommandType.Text;
command.Connection = connection;
command.Parameters.Add(new SqlParameter("#EventType", _eventType));
command.Parameters.Add(new SqlParameter("#PackageName", _packageName));
command.Parameters.Add(new SqlParameter("#TaskName", _taskName));
command.Parameters.Add(new SqlParameter("#EventCode", _errorCode ?? string.Empty));
command.Parameters.Add(new SqlParameter("#EventDescription", _errorDescription ?? string.Empty));
command.Parameters.Add(new SqlParameter("#PackageDuration", _packageDuration));
command.Parameters.Add(new SqlParameter("#Host", _machineName));
command.Parameters.Add(new SqlParameter("#ExecutionID", _executionid));
command.Parameters.Add(new SqlParameter("#handlerdatetime", _handlerdatetime));
command.ExecuteNonQuery();
}
return DTSExecResult.Success;
}
catch (Exception exc)
{
componentEvents.FireError(0, "CustomLoggingTask-Execute", "Task Errored: " + exc.ToString(), "", -1);
return DTSExecResult.Failure;
}
}
private void ReadVariables(VariableDispenser variableDispenser)
{
variableDispenser.LockForRead("System::StartTime");
variableDispenser.LockForRead("System::PackageName");
variableDispenser.LockForRead("System::SourceName");
variableDispenser.LockForRead("System::MachineName");
variableDispenser.LockForRead("System::ExecutionInstanceGUID");
variableDispenser.LockForRead("System::EventHandlerStartTime");
bool includesError = variableDispenser.Contains("System::ErrorCode");
if (includesError)
{
variableDispenser.LockForRead("System::ErrorCode");
variableDispenser.LockForRead("System::ErrorDescription");
}
Variables vars = null;
variableDispenser.GetVariables(ref vars);
DateTime startTime = (DateTime)vars["System::StartTime"].Value;
_packageDuration = DateTime.Now.Subtract(startTime).TotalSeconds;
_packageName = vars["System::PackageName"].Value.ToString();
_taskName = vars["System::SourceName"].Value.ToString();
_machineName = vars["System::MachineName"].Value.ToString();
_executionid = vars["System::ExecutionInstanceGUID"].Value.ToString();
_handlerdatetime = (DateTime)vars["System::EventHandlerStartTime"].Value;
if (includesError)
{
_errorCode = vars["System::ErrorCode"].Value.ToString();
_errorDescription = vars["System::ErrorDescription"].Value.ToString();
}
// release the variable locks.
vars.Unlock();
// reset the dispenser
variableDispenser.Reset();
}
}
}
In System.Data.Common you will find the base classes that the concrete database types (SqlConnection, OleDbConnection, SqlDataAdapter etc.) are all derived from.
If you use those, ie. DbConnection, it doesn't matter which of the concrete implementations you are working with, the code will be the same.
DbConnection connection = connections[_connectionName]
.AcquireConnection(transaction) as DbConnection;
Then, you can call DbConnection.BeginTransaction() instead of SqlConnection.BeginTransaction() and it doesn't matter whether the connection is OleDbConnection or SqlConnection.
This will work as long as all the methods you need to call are inherited from DbConnection.
The rest of your code could use the base types as well, so DbTransaction, DbDataAdapter, DbDataReader etc.
Because your AquireConnection() method does not return a concrete connection type, you are able to take advantage of Dependency Injection and write code that is not implementation-specific.
Specifically, for an insert you'll have something like this:
DbCommand command = null;
if (connection is SqlConnection)
command = new SqlCommand();
else if (connection is OleDbConnection)
command = new OleDbCommand();
command.CommandText = "INSERT STATEMENT HERE";
command.Connection = connection;
command.ExecuteNonQuery();
Don't forget that you will need connection.Close() somewhere. (Or ReleaseConnection if you are using ConnectionManager)
Since the connections[_connectionName].AcquireConnection(transaction); part of your code has nothing to do with a SqlConnection (you are just casting to SqlConnection afterwards), you should just be able to declare connection with var and then do anything with connection you need to.
var connection = connections[_connectionName].AcquireConnection(transaction);
Once you have your connection object, you can cast it to whatever connection type you need to and perform the same operation as if you had declared your connection variable as that type. E.g.
if(connection is DbConnection)
{
// ((DbConnection)connection).SomethingToDoWithDbConnection
}
if(connection is SqlConnection)
{
// ((SqlConnection)connection).SomethingToDoWithSqlConnection
}
if(connection is OleDbConnection)
{
// ((OleDbConnection)connection).SomethingToDoWithOleDbConnection
}
Wondering if I am going about this the right way. I am creating a C# application that loads a few variables from my App.Config.xml file. I am loading these into a "config" class (Config.cs) along with other variables that I am loading from a MySQL database. This is what my class looks like so far:
class Config
{
public static string ServerHostname = ConfigurationManager.AppSettings["ServerHostname"];
public static string SoftwareVersion = "v0.1a";
public static int StationID = DBConnector.GetStationID();
public static string StationDescription = DBConnector.GetStationDescription();
public static string StationName = ConfigurationManager.AppSettings["StationName"];
}
I am using Config.StationName to pull the Config.StationID and Config.StationDescription from a MySQL database like this in DBConnector.cs:
public static int GetStationID()
{
try
{
MySqlConnection conn = new MySqlConnection(connStr);
conn.Open();
string sql = "select station_id from station_master where station_name = #station_name limit 1";
MySqlCommand cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("#station_name", Config.StationName);
object result = cmd.ExecuteScalar();
conn.Close();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
ErrorConnection += ex.ToString();
return 0;
}
}
public static string GetStationDescription()
{
try
{
MySqlConnection conn = new MySqlConnection(connStr);
conn.Open();
string sql = "select station_description from station_master where station_id = '" + Config.StationID +"' limit 1";
MySqlCommand cmd = new MySqlCommand(sql, conn);
// cmd.Parameters.AddWithValue("#station_name", Config.StationName.ToString());
object result = cmd.ExecuteScalar();
conn.Close();
MessageBox.Show(sql);
return (result.ToString());
}
catch (Exception ex)
{
ErrorGenericDBException += ex.ToString();
MessageBox.Show(ErrorGenericDBException);
return "Error";
}
}
The DBConnector.GetStationID class works fine. It returns the int value of my station. But when I try to display the Config.StationDescription, it throws an exception of System.NullReferenceException: Object reference not set to an instance of an object at Namespace.DBConnector.GetStationDescription().
I thought that since I was using a static class for Config.StationName, i don't have to create an instance to refer to it. I need help to understand why its throwing an exception.
To me it looks like you might be mixing up what part of the code has the problem. Since you're saying that it's GetSTationDescription that throws, I'm not sure why you then assume StationName has a problem?
I'd take a look at the line object result = cmd.ExecuteScalar();, if you put a breakpoint on the line after that, does result have a value?
Another issue is that you're currently only closing the connection if an exception is not thrown, it would be safer to have the conn.Close(); inside a finally statement (or use a using statement so you don't have to worry about closing it).
I know how to change size database on SQL (in SQL Server 2005 Express Edition)
ALTER DATABASE Accounting
MODIFY FILE
(NAME = 'Accounting',
SIZE = 25)
How to change size database useing C#?
Submit the DDL via an ExecuteNonQuery command:
mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"ALTER DATABASE Accounting MODIFY FILE (NAME = 'Accounting', SIZE = 25) ";
mySqlConnection.Open();
int result = mySqlCommand.ExecuteNonQuery();
mySqlConnection.Close();
Similar examples can be found here (showing issues related to snapshot isolation, but the ideais basically the same):
http://msdn.microsoft.com/en-us/library/tcbchxcb.aspx
Following example will give you a better overview. Having defined parameters you can pass some stuff that not necessary has to be static. Using using will make sure everything is disposed/closed properly (and or reused if necessary).
public const string sqlDataConnectionDetails = "Data Source=YOUR-SERVER;Initial Catalog=YourDatabaseName;Persist Security Info=True;User ID=YourUserName;Password=YourPassword";
public static void ChangeDatabaseSize(int databaseSize) {
const string preparedCommand = #"
ALTER DATABASE Accounting
MODIFY FILE
(NAME = 'Accounting', SIZE = #size)
";
using (var varConnection = SqlConnectOneTime(sqlDataConnectionDetails))
using (SqlCommand sqlWrite = new SqlCommand(preparedCommand, varConnection)) {
sqlWrite.Parameters.AddWithValue("#size", databaseSize);
sqlWrite.ExecuteNonQuery();
}
}
This is supporting method that makes it easy to establish connection everytime you want to write/read something to database.
public static SqlConnection SqlConnectOneTime(string varSqlConnectionDetails) {
SqlConnection sqlConnection = new SqlConnection(varSqlConnectionDetails);
try {
sqlConnection.Open();
} catch {
DialogResult result = MessageBox.Show(new Form {
TopMost = true
}, "No connection to database. Do you want to retry?", "No connection (000001)", MessageBoxButtons.YesNo, MessageBoxIcon.Stop);
if (result == DialogResult.No) {
if (Application.MessageLoop) {
// Use this since we are a WinForms app
Application.Exit();
} else {
// Use this since we are a console app
Environment.Exit(1);
}
} else {
sqlConnection = SqlConnectOneTime(varSqlConnectionDetails);
}
}
return sqlConnection;
}