LocalDB changes persist in different mdf files - Why? - c#

I am trying to rebuild an application that originally used sqlite to now use 'localdb'. (I want an application that can create its own database locally and at runtime without requiring a pre-installed instance of sql server or sql express on the target machine)
I want to move away from using a 'third party' library (sqlite) as experience has told me it can be a pain to get it working from scratch, and towards something supposedly more straightforward to get up and running from scratch.
Using code copied (and slightly modified) from the web I have managed to create an mdf file dynamically/programmatically, but I am puzzled by what happens if I run it more than once, even if I choose a new filename each time. Namely it seems to somehow keep the changes/additions made on each run. Below is the relevant code...
public partial class Form1 : Form
{
SqlConnection conn;
public void CreateSqlDatabase(string filename)
{
string databaseName =
System.IO.Path.GetFileNameWithoutExtension(filename);
conn = new SqlConnection(
String.Format(
#"Data Source=(LocalDB)\v11.0;Initial Catalog=master;Integrated Security=True"
));
conn.Open();
using (var command = conn.CreateCommand())
{
command.CommandText =
String.Format(
"CREATE DATABASE {0} ON PRIMARY (NAME={0}, FILENAME='{1}')"
, databaseName, filename);
command.ExecuteNonQuery();
command.CommandText =
String.Format("EXEC sp_detach_db '{0}', 'true'", databaseName);
command.ExecuteNonQuery();
}
conn.Close();
}
private void button1_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
CreateSqlDatabase(openFileDialog1.FileName);
}
}
private void button2_Click(object sender, EventArgs e)
{
conn.Open();
SqlCommand comm = conn.CreateCommand();
comm.CommandText =
"create table mytable (id int, name nvarchar(100))";
comm.ExecuteNonQuery();
comm.CommandText =
"insert into mytable (id,name) values (10,'testing')";
comm.ExecuteNonQuery();
comm.CommandText = "select * from mytable";
SqlDataReader reader = comm.ExecuteReader();
while (reader.Read())
{
textBox1.Text +=
reader["id"].ToString() + ", " + reader["name"].ToString() + "\r\n";
}
conn.Close();
}
}
If I run the app once It runs through fine.
If I run the app a second time, and choose a different filename for the database it tells me 'mytable' already exists.
If I comment out the create table code it runs, but the select query returns multiple rows indicating multiple inserts (one for each time the app runs)
I am just seeking to understand why this happens. Do I need to delete database/table each time if I want the app to behave as if it has created the database/table from scratch on each subsequent run?

You have initial catalog 'master' in your connection string. Are you sure you haven't created the tables in the master database instead of the newly created database?
After the creation & detach of the database file, you could try and change your connection to:
SqlConnection con = new SqlConnection(#"Data Source=(LocalDB)\v11.0;Integrated Security=True;AttachDbFilename=c:\xxx\xxx\xxx.mdf");

Related

How to connect SQLite with my C# application

i have a problem with making a local database into my c# project and creating it..
I tried first with making a Microsoft Sql Server but the problem is that i need to make app which should run on every pc. The app should input data from user , and collect it to the database, and on every start of program, the database should be filled with the leftover of earlier input.. What you suggest me to do?
First to connect your c# application with sqlite you should start with getting connection string
private static string executableLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
private static string oldconnectionstring = Path.Combine(executableLocation, "YourDB.db");
private static string connectionString = "Data Source =" + oldconnectionstring.ToString();
After getting connection, to add your input to database follow below steps
using (SQLiteConnection conn = new SQLiteConnection(connectionString))
{
//Open connection to DB
conn.Open();
//Query to be fired
string sql = "Your Query to insert rows";
//Executing the query
using (SQLiteCommand cmd = new SQLiteCommand(sql, conn))
{
//Executing the query
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
//Close connection to DB
conn.Close();
}

How do you delete a generated mdf file with open connections?

I have created a database with this code:
public static void CreateDatabase(string databasePath)
{
AppDomain.CurrentDomain.SetData("DataDirectory", databasePath);
using (var connection = new System.Data.SqlClient.SqlConnection(#"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog=master; Integrated Security=true;;"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText =
String.Format("CREATE DATABASE {0} ON PRIMARY (NAME={0}, FILENAME='{1}')", "CoolDatabase", databasePath + #"\database.mdf");
command.ExecuteNonQuery();
command.CommandText =
String.Format("EXEC sp_detach_db '{0}', 'true'", "CotanDB");
command.ExecuteNonQuery();
}
connection.Close();
}
}
Which creates a nice working .mdf file for my unit tests. However, after running all tests on it, I want to remove it again so it doesn't take up space.
I tried this:
public static void DestroyDatabase(string databasePath)
{
if (File.Exists(databasePath + #"\database.mdf"))
{
File.Delete(databasePath + #"\database.mdf");
}
if (File.Exists(databasePath + #"\database_log.ldf"))
{
File.Delete(databasePath + #"\database_log.ldf");
}
}
But that throws an error
The process cannot access the file 'path to the database\database.mdf'
because it is being used by another process.
So I tried to close all connections to my database so I could drop it and delete the file. However, this does not work:
var server = new Server();
server.KillDatabase(databasePath + #"\database.mdf");
Which throws
Failed to connect to server ..
How do I destroy my local database file?
Edit: I tried the code in the answer in the comments:
using (var connection = new System.Data.SqlClient.SqlConnection(#"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog=master; Integrated Security=true;;"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText =
String.Format("USE master");
command.ExecuteNonQuery();
command.CommandText =
String.Format("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", "CotanDB");
command.ExecuteNonQuery();
command.CommandText =
String.Format("DROP DATABASE {0}", "CoolDatabase");
command.ExecuteNonQuery();
}
connection.Close();
}
Followed by the same File.Delete code. But this throws:
User does not have permission to alter database "CoolDatabase", the
database does not exist, or the database is not in a state that allows
access checks.
Figured it out myself. If you call SqlConnection.ClearPool(connection), all connections to your mdf file are dropped, after which you can easily delete it.
Looking back at this, this is a horrible solution and should be avoided, as this construction means your unit tests are now dependent on an external database. Good luck trying to run this in an Azure Pipeline.
If you have the option, use an EntityFramework InMemory database:
https://learn.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli

App.config acts different than the regular way

My connection string used to be like this:
SqlConnection cn = new SqlConnection(#"Data Source=.\SQLEXPRESS;AttachDbFilename=G:\I.S\C#\billingSystem\Store.mdf;Integrated Security=True;User Instance=True");
so i can (insert, update, delete) with this:
SqlCommand cmd = new SqlCommand("SQLSTATEMENT", cn);
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
I've seen a tutorial on youtube called "Deploy C# Project", when he used
System.Configuration;
and the app.config file, so I followed the tutorial exact the same way and it works.
The problem that after I (insert, update, delete) at runtime everything is okay until I close the application it's like I've did nothing at all the data that I've inserted is gone!.
I need to save the changes into the table at runtime.
Some info: Winforms app, sample from my code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="SchoolProjectConnectionString"
connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\SchoolDB.mdf;Integrated Security=True;User Instance=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
namespace SchoolProject
{
public partial class Main : Form
{
SqlConnection cn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SchoolProjectConnectionString"].ToString());
public Main()
{
InitializeComponent();
}
private void button4_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void button1_Click(object sender, EventArgs e)
{
string myValue = Interaction.InputBox("Enter ID", "ID", "", 100, 100);
string myValue0 = Interaction.InputBox("Enter Name", "Name", "", 100, 100);
int X = Convert.ToInt32(myValue);
SqlCommand cmd = new SqlCommand("Insert into Student(ID, Student) Values ('" + X + "','" + myValue0 + "')", cn);
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
private void button2_Click(object sender, EventArgs e)
{
string myValue3 = Interaction.InputBox("Enter ID", "ID", "", 100, 100);
int X = Convert.ToInt32(myValue3);
SqlCommand cmd = new SqlCommand("SELECT * FROM Student WHERE id = '" + X + "'", cn);
cn.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
MessageBox.Show(reader["Student"].ToString());
}
}
cn.Close();
}
private void button3_Click(object sender, EventArgs e)
{
Report R = new Report();
R.Show();
}
}
}
The whole User Instance and AttachDbFileName= approach is flawed - at best! When running your app in Visual Studio, it will be copying around the .mdf file (from your App_Data directory to the output directory - typically .\bin\debug - where you app runs) and most likely, your INSERT works just fine - but you're just looking at the wrong .mdf file in the end!
If you want to stick with this approach, then try putting a breakpoint on the myConnection.Close() call - and then inspect the .mdf file with SQL Server Mgmt Studio Express - I'm almost certain your data is there.
The real solution in my opinion would be to
install SQL Server Express (and you've already done that anyway)
install SQL Server Management Studio Express
create your database in SSMS Express, give it a logical name (e.g. Store)
connect to it using its logical database name (given when you create it on the server) - and don't mess around with physical database files and user instances. In that case, your connection string would be something like:
Data Source=.\\SQLEXPRESS;Database=Store;Integrated Security=True
and everything else is exactly the same as before...
Also: you should read up on SQL injection and change your code to avoid that - it's the #1 security risk for apps. You should use parametrized queries in ADO.NET instead - those are safe, and they're faster, too!

How to add/edit/retrieve data using Local Database file in Microsoft Visual Studio 2012

I want to get into developing applications that use databases. I am fairly experienced (as an amateur) at web based database utilization (mysql, pdo, mssql with php and old style asp) so my SQL knowledge is fairly good.
Things I have done already..
Create forms application
Add four text boxes (first name, last name, email, phone)
Added a datagrid control
Created a database connection using 'Microsoft SQL Server Database File (SqlClient)'
Created a table with fields corresponding to the four text boxes.
What I want to be able to do now is, when a button is clicked, the contents of the four edit boxes are inserted using SQL. I don't want to use any 'wrapper' code that hides the SQL from me. I want to use my experience with SQL as much as possible.
So I guess what I am asking is how do I now write the necessary code to run an SQL query to insert that data. I don't need to know the SQL code obviously, just the c# code to use the 'local database file' connection to run the SQL query.
An aside question might be - is there a better/simpler way of doing this than using the 'Microsoft SQL Server Database File' connection type (I have used it because it looks like it's a way to do it without having to set up an entire sql server)
The below is inserting data using parameters which I believe is a better approach:
var insertSQL = "INSERT INTO yourTable (firstName, lastName, email, phone) VALUES (firstName, lastName, email, phone)";
string connectionString = "Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI; User ID=userid;Password=pwd;"
using (var cn = new SqlCeConnection(connectionString))
using (var cmd = new SqlCeCommand(insertSQL, cn))
{
cn.Open();
cmd.Parameters.Add("firstName", SqlDbType.NVarChar);
cmd.Parameters.Add("lastName", SqlDbType.NVarChar);
cmd.Parameters.Add("email", SqlDbType.NVarChar);
cmd.Parameters.Add("phone", SqlDbType.NVarChar);
cmd.Parameters["firstName"].Value = firstName;
cmd.Parameters["lastName"].Value = lastName;
cmd.Parameters["email"].Value = email;
cmd.Parameters["phone"].Value = phone;
cmd.ExecuteNonQuery();
}
This is selecting data from database and populating datagridview:
var dt = new DataTable();
string connectionString = "Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI; User ID=userid;Password=pwd;"
using (var cn = new SqlCeConnection(connectionString )
using (var cmd = new SqlCeCommand("Select * From yourTable", cn))
{
cn.Open();
using (var reader = cmd.ExecuteReader())
{
dt.Load(reader);
//resize the DataGridView columns to fit the newly loaded content.
yourDataGridView.AutoSize = true; yourDataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
//bind the data to the grid
yourDataGridView.DataSource = dt;
}
}
This first example is an over view based upon how I think it will be easier to understand but this is not a recommended approach due to vulnerability to SQL injection (a better approach further down). However, I feel it is easier to understand.
private void InsertToSql(string wordToInsert)
{
string connectionString = Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI; User ID=myDomain\myUsername;Password=myPassword;
string queryString = "INSERT INTO table_name (column1) VALUES (" + wordToInsert + ")"; //update as you feel fit of course for insert/update etc
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open()
SqlDataAdapter adapter = new SqlDataAdapter();
SqlCommand command = new SqlCommand(queryString, connection);
command.ExecuteNonQuery();
connection.Close();
}
}
I would also suggest wrapping it in a try/catch block to ensure the connection closes if it errors.
I am not able to test this but I think it is OK!
Again don't do the above in live as it allows SQL injection - use parameters instead. However, it may be argued it is easier to do the above if you come from PHP background (just to get comfortable).
This uses parameters:
public void Insert(string customerName)
{
try
{
string connectionString = Data Source=myServerAddress;Initial Catalog=myDataBase;Integrated Security=SSPI; User ID=myDomain\myUsername;Password=myPassword;
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
connection.Open() SqlCommand command = new SqlCommand( "INSERT INTO Customers (CustomerName" + "VALUES (#Name)", connection);
command.Parameters.Add("#Name", SqlDbType.NChar, 50, " + customerName +");
command.ExecuteNonQuery();
connection.Close();
}
catch()
{
//Logic in here
}
finally()
{
if(con.State == ConnectionState.Open)
{
connection.Close();
}
}
}
And then you just change the SQL string to select or add!

Inserting records permanetly into sql server using c#

I created a database in sql server express edition, in that i create a table called employee. Now i am able to inserting rows(records) dynamically into table successfully, and i can also read those records successfully. But the problem is the values which are inserted dynamically are stored temporarily. When i close and reopen the application the previous inserted records are not available. can u please suggest me what can i do to save records permanently into the database.
thanking you.....
This is my code used to inserting the records into sql server database. Please help me out of this problem...
namespace VACS_practice
{
public partial class Form1 : Form
{
string m_sVehicleNo, m_sName, m_sFlatNo, m_sImagpath;
System.Data.SqlClient.SqlConnection Con;
System.Data.SqlClient.SqlCommand Cmd;
string ConString = #"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\VACSDB.mdf;Integrated Security=True;User Instance=True";
public Form1()
{
InitializeComponent();
}
private void btnAddClick(object sender, EventArgs e)
{
Con = new SqlConnection(ConString);
m_sVehicleNo = m_VehicleNo.Text;
m_sName = m_Name.Text;
m_sFlatNo = m_Phno.Text;
//m_sImagpath = m_ImgPath.Text;
Cmd = new SqlCommand("INSERT INTO ResidentDB ([R_VehNo],[R_Name],[R_PhNo]) VALUES ('" + m_sVehicleNo + "','" + m_sName + "','" + m_sFlatNo + "')", Con);
Con.Open();
Cmd.ExecuteNonQuery();
Con.Close();
MessageBox.Show("Inserted successfully");
// this.Close();
}
Almost certainly you are not committing your changes. If you are running transactions then you must commit.
Alternatively, you are making changes in your in-memory versions, that are not connected to the database at all.

Categories