SQLite is creating a disk file for In-memory DB - c#

I tried to create a shareable in-memory database as per the documentation provided on SQLite Site. But I end up finding the solution to the problem.
var connectionString = "Data Source=sharedmemdb;Mode=Memory;Cache=Shared";
using (var connection1 = new SQLiteConnection(connectionString))
{
connection1.Open();
var command1 = connection1.CreateCommand();
command1.CommandText =
"CREATE TABLE Message ( Text TEXT );" +
"INSERT INTO Message ( Text ) VALUES ( 'Is there anybody out there?' );";
command1.ExecuteNonQuery();
using (var connection2 = new SQLiteConnection(connectionString))
{
connection2.Open();
var command2 = connection2.CreateCommand();
command2.CommandText = "SELECT Text FROM Message;";
var message = command2.ExecuteScalar() as string;
}
}
If I execute this code, it will create in-memory DB named as sharedmemdb and shared cache is enabled while making the connection, so this connection accessible to other connections also. If I run this first time this works pretty fine but if I close the application and run again it throws error "Table Message already exists" and this looks very strange as I created the table in-memory and this should not be available if application restarts.
After getting this error, I looked into the application directory and found the file "sharedmemdb" which means SQLite is not creating the shareable in-memory DB.
Any clue why this is happening?
After moving command to using block:
var connectionString = "Data Source =sharedmemdb; Mode = Memory; Cache = Shared";
using (var connection1 = new SQLiteConnection(connectionString))
{
connection1.Open();
using (var command1 = connection1.CreateCommand())
{
command1.CommandText =
"CREATE TABLE Message ( Text TEXT );" +
"INSERT INTO Message ( Text ) VALUES ( 'Is there anybody out there?' );";
command1.ExecuteNonQuery();
}
using (var connection2 = new SQLiteConnection(connectionString))
{
connection2.Open();
using (var command2 = connection2.CreateCommand())
{
command2.CommandText = "SELECT Text FROM Message;";
var message = command2.ExecuteScalar() as string;
}
}
}

System.Data.SQLite doesn't support the "Mode" parameter (that's Microsoft.Data.Sqlite).
However, in System.Data.SQLite you can use the "FullUri" parameter to pass arguments directly to SQLite, so you can achieve what you wanted by changing your connection string to
FullUri=file:mem.db?mode=memory&cache=shared
Protocol^ ^ ^ ^
DB Name -----| | |
Use memory mode ----+ |
Use shared cache ---------------+
(The first line being the actual connection string, the next couple of lines breaking it down)

Related

Set connection string for database access for production build Winforms app

I am working on a Winforms project, and I am just setting up the setup file for the installation of the program.
Scenario
The connection string I have set for my use is like this:
SqlConnection con = new SqlConnection(#"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=F:\Application\MyAppv1.0\MyApp\DB.mdf;Integrated Security=True");
return con;
Here, I use local DB from my personal location.
Question
On installation on the client's PC, the database is also attached, so the installed files will be on C:\Program Files\Author\MyAppv1.0. When I use the same connection string, the app shows an exception. So, should I update my connection string for that location during the setup creation? (I am using MSI).
Is there any commonly followed approach for this scenario?
My aim is to make the installed app use the DB that is also on the same location of installation named DB.mdf and is provided with setup.
Thanks guys in advance.
Do not use AttachDbFilename, it has many issues. Instead, attach your database normally using CREATE DATABASE...FOR ATTACH.
At startup of your app, you can connect to the server using master as the current DB, and check for existence of your DB. If it's not there you can create it.
private static void CheckDbExistence(string connectionString)
{
const string query = #"
IF NOT EXISTS (SELECT 1
FROM sys.databases
WHERE name = 'MyDb')
BEGIN
DECLARE #sql nvarchar(max) = N'
CREATE DATABASE MyDb ON
(FILENAME = ' + QUOTENAME(#mdf, '''') + '),
(FILENAME = ' + QUOTENAME(#ldf, '''') + ')
FOR ATTACH;
';
EXEC (#sql);
END;
";
var csb = new SqlConnectionStringBuilder(connectionString);
csb.Initial Catalog = "master";
using (var conn = new SqlConnection(csb.ConnectionString))
using (var comm = new SqlCommand(query, conn))
{
comm.Parameters.Add("#mdf", SqlDbType.NVarChar, 255).Value = Path.Combine(Environment.CurrentDirectory, "MyDb.mdf");
comm.Parameters.Add("#ldf", SqlDbType.NVarChar, 255).Value = Path.Combine(Environment.CurrentDirectory, "MyDb.ldf");
conn.Open();
comm.ExecuteNonQuery();
}
}

How to backup a database in WPF with C# and SQL Server? [duplicate]

This question already has answers here:
Backup Permissions
(5 answers)
Closed 1 year ago.
I'm trying to back up my database using this C# code How to backup and restore SQL Server in WPF with C# and Entity Framework
private static void CreateBackup(string databaseName, string backupFilePath)
{
GlobalConfig gb = new GlobalConfig();
string connectionString = gb.GetConnectionString();
backupFilePath = backupFilePath + "\\" + databaseName + ".bak";
backupFilePath = #""+backupFilePath;
var backupCommand = "BACKUP DATABASE #databaseName TO DISK = #backupFilePath";
using (var conn = new SqlConnection(connectionString))
using (var cmd = new SqlCommand(backupCommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#databaseName", databaseName);
cmd.Parameters.AddWithValue("#backupFilePath", backupFilePath);
cmd.ExecuteNonQuery();
}
}
CreateBackup("Test","C:\Desktop\Backup\\Test.bak");
But I got this error :
Cannot open backup device 'C:\Desktop\Backup\Test.bak'. Operating system error 5(Access is denied.).
What I'm doing wrong with this code?
How can I fix this error?
The SQL Server process typically does not run with the permissions of the currently logged in user, therefore it cannot access the users desktop (nor most of the folders of the user or any network folders). It is not possible to freely choose the folder for the backup.
Your best solution is to export to a folder where the server process has access to (i.e. the system temp folder) and then copy the backup from there to wherever you want it.
private static void CreateBackup(string databaseName, string backupFilePath)
{
GlobalConfig gb = new GlobalConfig();
string connectionString = gb.GetConnectionString();
// Create the backup in the temp directory (the server should have access there)
var backup = Path.Combine(Path.GetTempPath(), "TemporaryBackup.bak");
var backupCommand = "BACKUP DATABASE #databaseName TO DISK = #backup";
using (var conn = new SqlConnection(connectionString))
using (var cmd = new SqlCommand(backupCommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#databaseName", databaseName);
cmd.Parameters.AddWithValue("#backup", backup);
cmd.ExecuteNonQuery();
}
File.Copy(backup, backupFilePath); // Copy file to final location
}
I have successfully backed up SQL Server databases using Microsoft.SqlServer.Management.Smo.Backup, might want to try that. Mine was in VB years ago but it is still working today. Here is the VB code if it helps:
Dim mySourceServer As New Server(My.Settings.SQLInstance)
Dim bkpDBFullWithCompression As New Backup()
' Specify whether you want to back up database or files or log
Me.Cursor = Cursors.WaitCursor()
bkpDBFullWithCompression.Action = BackupActionType.Database
' Specify the name of the database to back up
bkpDBFullWithCompression.Database = _sBackupDatabaseName
bkpDBFullWithCompression.CompressionOption = BackupCompressionOptions.[On]
bkpDBFullWithCompression.Devices.AddDevice(_sBackupFilePath, DeviceType.File)
bkpDBFullWithCompression.BackupSetName = _sBackupDatabaseName + " database Backup - Compressed"
bkpDBFullWithCompression.BackupSetDescription = _sBackupDatabaseName + " database - Full Backup"
Try
bkpDBFullWithCompression.SqlBackup(mySourceServer)
Catch ex As SmoException
blSuccess = False '
Me.Cursor = Cursors.Default
End Try
This solution ( Check Local System account instead of This account ) worked for me, but I didn't have any idea if it's a good solution for security or no.
You can find the LogOn tab setting under this :
Services -> SQL Server -> Properties -> Log on

C# SQL Query - If statement failing

I am trying to learn C# and I'm writing a system where you have to log in, I'm storing the data in a database and loading in with code. The data is loaded in with no errors and I can Console.WriteLine it and it's all fine, but when I run comparison on it it always fails. Here is the relevant code.
Edit: I have tried without using the $ in the string comparison and it still doesn't work
private void login_button_Click(object sender, EventArgs e)
{
// App.config stores configuration data
// System.Data.SqlClient provides classes
// for accessing a SQL Server DB
// connectionString defines the DB name, and
// other parameters for connecting to the DB
// Configurationmanager provides access to
// config data in App.config
string provider = ConfigurationManager.AppSettings["provider"];
string connectionString = ConfigurationManager.AppSettings["connectionString"];
// DbProviderFactories generates an
// instance of a DbProviderFactory
DbProviderFactory factory = DbProviderFactories.GetFactory(provider);
// The DBConnection represents the DB connection
using (DbConnection connection =
factory.CreateConnection())
{
// Check if a connection was made
if (connection == null)
{
Console.WriteLine("Connection Error");
Console.ReadLine();
return;
}
// The DB data needed to open the correct DB
connection.ConnectionString = connectionString;
// Open the DB connection
connection.Open();
// Allows you to pass queries to the DB
DbCommand command = factory.CreateCommand();
if (command == null)
{
return;
}
// Set the DB connection for commands
command.Connection = connection;
// The query you want to issue
command.CommandText = $"SELECT * FROM Users WHERE Username = '{username_input.Text}'";
// DbDataReader reads the row results
// from the query
using (DbDataReader dataReader = command.ExecuteReader())
{
dataReader.Read();
//while(dataReader.Read())
//{
if ($"{password_input.Text}" ==$"{dataReader["Password"]}")
{
MessageBox.Show("Logged in");
}
else
{
MessageBox.Show("Invalid Credentials!");
}
//}
}
}
}
}
Always use parameters instead of string concatenation in your queries. It guards against sql injection (not applicable to MS Access) and ensures you never has issues with strings that contain escape charaters.
I notice you probably have password as plain text, never store passwords in plain text!
In this particular case using ExecuteScalar simplifies the logic (IMO). If you were to want to return data and read it using a data reader then do not use * for your return. Specify your column names instead. This will guard your code against schema changes like columns being added or column order changes.
command.CommandText = "SELECT [Password] FROM [Users] WHERE [Username] = #userName";
// using DbCommand adds a lot more code than if you were to reference a non abstract implementation when adding parameters
var param = command.CreateParameter();
param.ParameterName = "#userName";
param.Value = username_input.Text;
param.DbType = DbType.String;
param.Size = 100;
command.Parameters.Add(param);
// compared with SqlDbCommand which would be 1 line
// command.Parameters.Add("#userName", SqlDbType.VarChar, 100).Value = username_input.Text;
var result = command.ExecuteScalar()?.ToString();
if(string.Equals(password_input.Text, result, StringComparison.Ordinal))
MessageBox.Show("Logged in");
else
MessageBox.Show("Invalid Credentials!");
Start off on the right foot with learning C# with some advice Ive seen in the comments already as well some additional advice below:
Parameterize your queries at the very minimum
The below way is Open to SQL injection
command.CommandText = $"SELECT * FROM Users WHERE Username = '{username_input.Text}'";
This instead should be written as: (Keep in mind there are shorter ways to write this but I'm being explicit since you are learning)
var usernameParam = new SqlParameter("username", SqlDbType.VarChar);
usernameParam.Value = username_input.Text;
command.Parameters.Add(usernameParam);
command.CommandText = "SELECT * FROM Users WHERE Username = #username";
Secondly, debugging is your friend. You need to add a breakpoint on the line that is failing and utilize the built in Visual Studio Watchers to look at your variables. This will tell you more information than a console.writeline() and solve more problems than you might imagine.

SQL Server : "require open and available connection"

I'm just learning databases to store a (large amount) of user entry data.
I have the following code, which checks a record and chooses whether to update or create new
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string sc1 = #"select count(*) from job1 where report = #report";
SqlCommand check = new SqlCommand(sc1, connection);
check.Parameters.AddWithValue("#report", jname);
// check if the report number already exists, if not make a new table otherwise insert
int test = (int)check.ExecuteScalar();
if (test > 0)
{
jobCardExistingTable(connection);
digCardExistingTable(connection);
//insert into existing table code
}
If I use either jobCardExistingTable or digCardExisting table, they work fine. If I use both, I get the error
require open and available connection
I assume that the first ExecuteNonQuery (which are contained in the jobCard and digCard methods) is doing something with the connection - can I keep this one open, or do I have to open a new one each time I call a method? Maybe I'm doing this all wrong anyways...each method is calling a new table in the database, should I be calling them all at once?
Edit: part of the issue is jobCardTable (digCardTable is identical, just a different query)
public void jobCardNewTable(SqlConnection connection)
{
using (connection)
{
string sc3 = "";
sc3 = #"INSERT INTO job1 (" + pv.jobstring + ") VALUES (" + pv.jobparam + ")";
SqlCommand cmd = new SqlCommand(sc3, connection);
queryParams(cmd, 0);
cmd.ExecuteNonQuery();
}
}
Edit: solved - realised that using{} disposes the connection. Took all the using{} out of the methods, and used a single using{} to encompass all the method calls and it works
You should not use using (connection) if you are using same connection in other part of code. using dispose connection and make unavailable for further connection.
So, your jobCardNewTable method implementation should be without using statement :
public void jobCardNewTable(SqlConnection connection)
{
string sc3 = "";
sc3 = #"INSERT INTO job1 (" + pv.jobstring + ") VALUES (" + pv.jobparam + ")";
SqlCommand cmd = new SqlCommand(sc3, connection);
queryParams(cmd, 0);
cmd.ExecuteNonQuery();
}
I would recommend to create new connection whenever you need it and dispose it.

Copy database offline mode

I have been using SMO for a while for transfering databases. It was pretty easy to handle from c# by using the TransferDatabase task.
For my current project this gets to slow. I have to switch to offline mode, where the database is detached and atached.
What is the least troublesome way to start such a process from c#? I know that there is SSIS, but if possible I would not like to use it. Installing SSIS on my machine is a bit painfull.
Just issue the SQL Server Detach and Attach command from c#
Something like this...
var sourceConnectionString = "Data Source=sourceServer;Initial Catalog=MyDB;Integrated Security=True;";
var destinationConnectionString = "Data Source=destinationServer;Initial Catalog=MyDB;Integrated Security=True;";
var sourceLocalPath = #"C:\MSSQL\DATA\MyDB.mdf";
var destinationLocalPath = #"C:\MSSQL\DATA\MyDB.mdf";
var sourceRemotePath = #"\\ServerNameA\ShareName\MyDB.mdf";
var destinationRemotePath = #"\\ServerNameB\ShareName\MyDB.mdf";
// Make connections
var sourceConnection = new SqlConnection(sourceConnectionString);
sourceConnection.Open();
var destinationConnection = new SqlConnection(destinationConnectionString);
destinationConnection.Open();
// Detach source database
var sourceCommand = new SqlCommand("sp_detach_db MyDB", sourceConnection);
sourceCommand.ExecuteNonQuery();
// Detach destination database
var destinationCommand = new SqlCommand("sp_detach_db MyDB", destinationConnection);
destinationCommand.ExecuteNonQuery();
// Copy database file
File.Copy(sourceRemotePath, destinationRemotePath);
// Re-attach source database
sourceCommand = new SqlCommand("CREATE DATABASE MyDbName ON (FILENAME = '" + sourceLocalPath + "') FOR ATTACH", sourceConnection);
sourceCommand.ExecuteNonQuery();
// Re-attach destination database
destinationCommand = new SqlCommand("CREATE DATABASE MyDbName ON (FILENAME = '" + destinationLocalPath + "') FOR ATTACH", destinationConnection);
destinationCommand.ExecuteNonQuery();
I do this work in my project.
you can use SqlBulkCopy class in C#. this class is very powerful and you can copy all of data with best performance.
this class make a bcp command and run it on your server. this commands are like this:
bcp pubs.dbo.authors out c: emppubauthors.bcp –n –Sstevenw –Usa –P
bcp pubs2.dbo.authors in c: emppubauthors.bcp –n –Sstevenw –Usa –P
there is many switches for this command. please see this code:
// Create source connection
SqlConnection source = new SqlConnection(connectionString);
// Create destination connection
SqlConnection destination = new SqlConnection(connectionString);
// Clean up destination table. Your destination database must have the
// table with schema which you are copying data to.
// Before executing this code, you must create a table BulkDataTable
// in your database where you are trying to copy data to.
SqlCommand cmd = new SqlCommand("DELETE FROM BulkDataTable", destination);
// Open source and destination connections.
source.Open();
destination.Open();
cmd.ExecuteNonQuery();
// Select data from Products table
cmd = new SqlCommand("SELECT * FROM Products", source);
// Execute reader
SqlDataReader reader = cmd.ExecuteReader();
// Create SqlBulkCopy
SqlBulkCopy bulkData = new SqlBulkCopy(destination);
// Set destination table name
bulkData.DestinationTableName = "BulkDataTable";
// Write data
bulkData.WriteToServer(reader);
// Close objects
bulkData.Close();
destination.Close();
source.Close();

Categories

Resources