Visual studio mdf database structure modified then lost after publish - c#

Service-based database is new to me. I would like to create a simple database application with:
Service-based database -> Dataset (mdf)
LINQ to SQL (L2S) Classes
This application will be installed on a lot of individual machines every instances has it's own mdf database.
Installation is done by Clickonce.
My problem is:
I publish my application and install it on user machines
Users put some data into the database
Turns out that we need another table or column
Publish the application again with the extended database and install on user machines
User starts with a new database and original data lost!
(If I am not modifying the database structure than all data is in the database after next Clickone update)
Questions:
If I made only alter table- or add table- like modifications is there any way to preserve data during the next Clickonce update?
Thank you in advance!
Dave

I have found a solution.
If you modify your database model in visual studio Clickonce will automatically recreate all tables after installation (newly published application with modified tables).
Clickonce save the old database to a specified location:
if (ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.DataDirectory != null)
string preDatabase = Path.GetFullPath(Path.Combine(ApplicationDeployment.CurrentDeployment.DataDirectory,#".pre\sampledatabase.mdf"));
This .pre directory is created by Clickonce. You always check if this file is exist or not. If it is exist you have to copy data from old tables to new tables OR you will loose all data from old tables!
How to copy data from one database to another very similar database? My answer is the following: copy all table to another table with SqlBulkCopy.
// Create source connection
using (
var source =
new SqlConnection(
String.Format(
#"Data Source=(LocalDB)\v11.0;AttachDbFilename={0};Integrated Security=True;",
preDatabase)))
{
source.Open();
// Create destination connection
using (var destination = new SqlConnection(Settings.Default.mdcdbConnectionString))
{
destination.Open();
DataTable dt = source.GetSchema("Tables");
foreach (string tablename in from DataRow row in dt.Rows select (string) row[2])
{
App.Logger.LogText(String.Format("Copying table {0}", tablename));
using (var cmd = new SqlCommand(String.Format("TRUNCATE TABLE {0}", tablename), destination))
{
cmd.ExecuteNonQuery();
//App.Logger.LogText(String.Format("truncate table {0}", tablename));
}
using (var cmd = new SqlCommand(String.Format("SELECT * FROM {0}", tablename), source))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
var bulkData = new SqlBulkCopy(destination)
{
DestinationTableName = tablename
};
// Set destination table name
bulkData.WriteToServer(reader);
// Close objects
bulkData.Close();
//App.Logger.LogText(String.Format("Copy success {0}", tablename));
}
}
}
destination.Close();
}
source.Close();
}
Wish you good coding!
Dave

Related

Best way to retrieve pdf attachments saved as varbinary(max) from SQL Server and save to a local drive using C#

I have few thousands attachments saved in a SQL Server database with column datatype varbinary(max). I want to retrieve all these files and save them to a local drive.
What is the best way to achieve that? I am not looking for code specifically but trying to understand all options so I can do this in C#.
Suggestions are appreciated. Thank you
You can do any of the followings:
Write a SQL script which will read the data from the table and save those to the disk. Here is how that can be done
You can write a C# script which will connect to the Database, Read and store the data as a file to a disk
Use the Id of the table as part of the file name to make it unique if you are storing all the files to a single folder.
Here's example code.
Note, the namespace System.Data.SqlClient is not referenced by a .NET Core project by default as done by .NET Framework; you have to manually add the System.Data.SqlClient NuGet package to the project.
using System.Data.SqlClient;
var connectionString = "Data Source=localhost;Initial Catalog=MyDatbase;Integrated Security=True;";
var outputFolder = #"C:\temp\";
using var conn = new SqlConnection(connectionString);
conn.Open();
var query = "select DocumentId, Contents from DocumentFile where ID >= 1234";
using var cmd = new SqlCommand(query);
cmd.Connection = conn;
var reader = cmd.ExecuteReader();
if (!reader.HasRows) throw new Exception("No rows!");
while (reader.Read())
{
var fileName = $"{reader["DocumentId"]}.pdf";
var data = (byte[])reader["Contents"];
if (data == null) throw new Exception("Contents is null");
using var writer = new BinaryWriter(File.OpenWrite(Path.Combine(outputFolder, fileName)));
writer.Write(data);
}

SQL CE Not updating database values from windows forms

I am building a inventory based application that can be directly run from any pc without any installation and so i am using SQL CE.. On Start, I am checking if database is present. If Not i am creating new database as :
try
{
string conString = "Data Source='ITM.sdf';LCID=1033;Password=ABCDEF; Encrypt = TRUE;";
SqlCeEngine engine = new SqlCeEngine(conString);
engine.CreateDatabase();
return true;
}
catch (Exception e)
{
//MessageBox.Show("Database Already Present");
}
The database is created properly and i can access the database to create tables as well.. The Problem i am facing is - I am inserting and updating records in windows form on button click with code :
using (SqlCeConnection connection = new SqlCeConnection(DatabaseConnection.connectionstring))
{
connection.Open();
String Name = NameTxt.Text.ToString();
String Phone = PhoneTxt.Text.ToString();
double balance = Double.Parse(BalanceTxt.Text.ToString());
String City = CityTxt.Text.ToString();
string sqlquery = "INSERT INTO Customers (Name,Phone,Balance,City)" + "Values(#name,#phone, #bal, #city)";
SqlCeCommand cmd = new SqlCeCommand(sqlquery, connection);
cmd.Parameters.AddWithValue("#name", Name);
cmd.Parameters.AddWithValue("#phone", Phone);
cmd.Parameters.AddWithValue("#bal", balance);
cmd.Parameters.AddWithValue("#city", City);
int x = cmd.ExecuteNonQuery();
if (x > 0)
{
MessageBox.Show("Data Inserted");
}
else
{
MessageBox.Show("Error Occured. Cannot Insert the data");
}
connection.Close();
}
and Updation Code is
using (SqlCeConnection connection = new SqlCeConnection(DatabaseConnection.connectionstring))
{
connection.Open();
int idtoedit = select_id_edit;
String Name = NameEditTxt.Text.ToString();
String Phone = metroTextBox1.Text.ToString();
String City = CityEditTxt.Text.ToString();
string sqlquery = "Update Customers Set Name = #name, Phone = #phone,City = #city where Id = #id";
SqlCeCommand cmd = new SqlCeCommand(sqlquery, connection);
cmd.Parameters.AddWithValue("#name", Name);
cmd.Parameters.AddWithValue("#phone", Phone);
cmd.Parameters.AddWithValue("#id", idtoedit);
cmd.Parameters.AddWithValue("#city", City);
int x = cmd.ExecuteNonQuery();
if (x > 0)
{
MessageBox.Show("Data Updated");
}
else
{
MessageBox.Show("Error Occured. Cannot Insert the data");
}
loadIntoGrid();
connection.Close();
}
Whenever i execute code for inserting and updating records - Records are reflected in datagrid filled with adapter from database table. But once i restart the application, values do not appear in database. Once in a million, values are reflected in database. I cannot understand the reason behind this issue.
I have reffered to these articles :
C# - ExecuteNonQuery() isn't working with SQL Server CE
Insert, Update, Delete are not applied on SQL Server CE database file for Windows Mobile
But since i am creating database programmatically - It is getting created directly to bin/debug directory and i cannot see it in solution explorer in visual studio for changing copy options
You probably rewrite your database file with a blank copy from your project.
See this answer. You should not store your database in a bin folder, rather you should find a place in user's or public profile in AppData or another folder, depending on your needs.
The connection string would look like this:
<connectionStrings>
<add name="ITMContext" connectionString="data source=|DataDirectory|\ITM.sdf';LCID=1033;Password=ABCDEF; Encrypt = TRUE;" providerName="System.Data.SqlServerCe.4.0">
You shuld deploy your db file with your custom code run at app's starup to a chosen location, see this ErikEJ blog: http://erikej.blogspot.cz/2013/11/entity-framework-6-sql-server-compact-4_25.html
private const string dbFileName = "Chinook.sdf";
private static void CreateIfNotExists(string fileName)
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// Set the data directory to the users %AppData% folder
// So the database file will be placed in: C:\\Users\\<Username>\\AppData\\Roaming\\
AppDomain.CurrentDomain.SetData("DataDirectory", path);
// Enure that the database file is present
if (!System.IO.File.Exists(System.IO.Path.Combine(path, fileName)))
{
//Get path to our .exe, which also has a copy of the database file
var exePath = System.IO.Path.GetDirectoryName(
new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
//Copy the file from the .exe location to the %AppData% folder
System.IO.File.Copy(
System.IO.Path.Combine(exePath, fileName),
System.IO.Path.Combine(path, fileName));
}
}
Check also this post.
You have several options to change this behavior. If your sdf file is
part of the content of your project, this will affect how data is
persisted. Remember that when you debug, all output of your project
(including the sdf) if in the bin/debug folder.
You can decide not to include the sdf file as part of your project and manage the file location runtime.
If you are using "copy if newer", and project changes you make to the database will overwrite any runtime/debug changes.
If you are using "Do not copy", you will have to specify the location in code (as two levels above where your program is running).
If you have "Copy always", any changes made during runtime will always be overwritten
The database file in your project can be different when debugging or not. Check also this answer. You might be writting your records to another copy of the database file.

How can create a new table in database if the table does not exist (c#)

Here is my code, it works when the database has the table "UserInformation"
public bool Save()
{
using (SqlConnection connection = new SqlConnection(ConnectionString)
{
connection.Open();
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
using (var adapter = new UserInformationTableAdapter())
{
adapter.Connection = connection;
adapter.Transaction = transaction;
var table = new HelloDataSet.UserInformationDataTable();
HelloDataSet.UserInformationRow row = table.NewUserInformationRow();
row.UserName = userName;
row.Password = password;
row.Brithday = brithday;
table.Rows.Add(row);
adapter.Update(table);
transaction.Commit();
return true;
}
}
catch (Exception e)
{
transaction.Rollback();
return false;
}
finally
{
connection.Close();
}
}
}
However, when there is no table in the database, it will not create the "UserInformation" table in the database, it will jump to "catch" exception in line "adapter.Update(table);"
So my question is how can I create a new table in database if there is no "UserInformation" table in it. In addition, if the database already has the table "UserInformation" can I add a new column "Position" in that table?
Finally, I got the answer and want to share it out. First, I have to say that I put my question in a wrong way. What I really want is I have an application, and this application is connected with a database. However, I am allowed user to switch database. So, when the user switch to a new database I would like the application copy the entire database structure (not including the data) from the old one to the new one. Also, if I make some change (could be add a new column for one or more table, or add another new table) for the database in my application code, I would like every other database know the updates and make the same change by running my new application code.
So, here is my solution. I write a framework called "SchemaManager." It will create an additional table in each database, this table contains the version of the database. So, every time when I run my application the "SchemaManager" will check my hard code database version number with the database version number, if my hard code database version number is greater than the database version number, the "SchemaManager" will check the change and do the update for me.
I know my solution is not the best, but this is what I did. If anyone have anyother solution, please share with me and other people.

Use OLEDB to read AccessFile from Stream to DataSet

I Use Oledb to read an AccessFile(.accdb) to DataSet, I don't know about table names or columns, The regular implementation is:
public void GetAccessDB(string filepath){
this.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source = " + filepath;
// get Table Names
this.TableNames = new List<string>();
using (System.Data.OleDb.OleDbConnection oledbConnection = new System.Data.OleDb.OleDbConnection(this.ConnectionString))
{
oledbConnection.Open();
System.Data.DataTable dt = null;
dt = oledbConnection.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
foreach (System.Data.DataRow row in dt.Rows)
{
string strSheetTableName = row["TABLE_NAME"].ToString();
if (row["TABLE_TYPE"].ToString() == "TABLE")
this.TableNames.Add(strSheetTableName);
}
oledbConnection.Close();
}
this.Dataset = new System.Data.DataSet();
using (System.Data.OleDb.OleDbConnection oledbConnection = new System.Data.OleDb.OleDbConnection(this.ConnectionString))
{
foreach (string table in this.TableNames)
{
string command = string.Format("SELECT * FROM {0};", table);
using (System.Data.OleDb.OleDbCommand cmd = new System.Data.OleDb.OleDbCommand(command, oledbConnection))
{
cmd.CommandType = System.Data.CommandType.Text;
oledbConnection.Open();
System.Data.OleDb.OleDbDataReader dr = cmd.ExecuteReader();
this.Dataset.Load(dr, System.Data.LoadOption.OverwriteChanges, table);
oledbConnection.Close();
}
}
}
}
But I need to get Access File from Stream, And I can't Write it on the Disk temporary, so what is your suggestion?
I need This overload of GetAccessDB(Stream AccessFile)?
I search and find This, but that's not clear for me, I need finally get DataSet by all Tables in Access File.
Does any one know about this?
I don't know any api function in OleDb for work with in-memory databases. Maybe, could you install a RAMDisk?
If you have control over the MS SQL Server, that's good news. I currently see 2 alternatives:
Create a CLR asssembly that will process (asynchronously is a good idea) the file once the insert is made in the uploaded files table. It would create a temporary MS Access file on the server by using the content of the uploaded file. Then, open it with OleDB, parse it and insert the information from it in a SQL table which maps the extracted information with the uploaded file record in the first table. Then, you could go and look for the data in this second table.
Another option would be to send to the SQL a command which will do the following:
Use the uploaded file bytes to create a file on the filesystem.
Then, use the file as a linked server
Use SELECT to query the Access database
You may have noticed that both options involve creating a (at least temporary) file on the SQL Server.

System.Data.sqlClient will only create default database on server

Background:
I am using sql statements to create a Temp database on a server which will store data until it is needed further by my client program.
Problem:
My sql statement to create the database works properly and creates the database with all the required specifications when run through Sql Management studio, on the other hand when my program executes the statement it only creates a database with the 'Default' settings except for the name.
Questions:
Why is this?
How can I make it create a database with my specifications
Sql statement:
CREATE DATABASE Temp ON PRIMARY(
NAME = Temp
, FILENAME = 'C:\Temp.mdf'
, SIZE = 2MB
, FILEGROWTH = 10%) LOG ON (
NAME = Temp_Log
, FILENAME = 'C:\Temp.ldf'
, SIZE = 1MB, MAXSIZE = 70MB
, FILEGROWTH = 10%)
Code:
public void AcuConvert()
{
using (DestD)
{
SqlCommand command = new SqlCommand();
DestD.Open();
command.Connection = DestD;
foreach (var item in Entity.SqlDestinationQueries.ToList())
{
command.CommandText = item.Query;
command.ExecuteNonQuery(); //This is where the command is run
}
foreach (var item in Entity.SystemQueries.ToList())
{
command.CommandText = item.Query.Replace("#Sys", SysD.Database);
command.ExecuteNonQuery();
}
foreach (var item in Entity.InsertQueries.ToList())
{
command.CommandText = item.Query.Replace("#Source", SourceD.Database); ;
command.ExecuteNonQuery();
}
}
}
Have you tried using SQL Server Management Objects instead of a raw SQL statement?
For example:
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
...
// Connect to the default instance
Server server = new Server();
// Establish new database details
Database database = new Database(server, "MyTempDB");
// Add primary filegroup details
database.FileGroups.Add(new FileGroup(database, "PRIMARY"));
// Set Primary datafile properties
DataFile primaryFile = new DataFile(database.FileGroups["PRIMARY"],
"MyTempDB_Data", "C:\\MyTempDB.mdf");
primaryFile.Size = 2048; // Sizes are in KB
primaryFile.GrowthType = FileGrowthType.Percent;
primaryFile.Growth = 10;
// Add to the Primary filegroup
database.FileGroups["PRIMARY"].Files.Add(primaryFile);
// Define the log file
LogFile logfile = new LogFile(database, "MyTempDB_Log", "C:\\MyTempDB_Log.ldf");
logfile.Size = 1024;
logfile.GrowthType = FileGrowthType.Percent;
logfile.Growth = 10;
logfile.MaxSize = 70 * 1024;
// Add to the database
database.LogFiles.Add(logfile);
// Create
database.Create();
database.Refresh();
You can connect to the server with specific credentials, too, of course:
http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.server.aspx
If, however, you're stuck with using text scripts to create your database, I'd ensure that your Initial Catalog is set correctly (i.e. to 'master') in your connection string and your user has the necessary permissions - CREATE DATABASE / CREATE ANY DATABASE / ALTER DATABASE. If this still doesn't give you any joy, try stripping out the rest of your C# code and run the create SQL independently of the other statements - it could be that there's a side-effect from a preceding query. Use Profiler to see exactly what's running as you add them back in.
Edit:
I tested your script against a local SQL Server instance (2012 SqlLocalDB) via a small C# program using SqlClient and it ran just fine after changing the file paths to ones I had write access to (root of C is protected by default). The only other amendment was that the Primary size had to start at 3MB or more. Any smaller and the default tables could not be created. This may be another avenue of investigation for you to explore.
Your alternative option could be to use the Process class to run the sqlcmd.exe
Eg.
var process = Process.Start(WORKING_PATH, argument);
%WORKING_PATH% being "C:\tools\sql\sqlcmd.exe"
%argument% being "C:\scripts\Create.sql"
I use this strategy to dump test data into test environments when bootstrapping acceptance test fixtures.
Cheers

Categories