I am using Npgsql version 4.1.3.1 as a .NET data provider for PostgreSQL. Here is my use case
For a database I have to create table's dynamically and table name is Guid which I passing to one method.
If particular table is exist then using table specific connection string (each table has own credential), I need to insert data into table.
If table not exist then using admin connection string (has ONLY permission to create a table), I need to create the table and then insert the record in respective table.
Currently I am using C# try/catch, within try I am trying to insert the record in table and if table is NOT exist, then in catch I am trying to create the table and try re-insert again.
private async Task<bool> Insert(Guid tableId, string name)
{
//here connection string is specific to table, which
//don't have permission to create any table
using (var conn = await GetTableConnectionString(tableId))
{
//open site specific connection
await conn.OpenAsync();
try
{
//insert data in table
await using (var cmd = new NpgsqlCommand($#"Insert into Tbl-{tableId:N} (
id,
name) Values (#id, #name)", conn))
{
cmd.Parameters.AddWithValue("id", Guid.NewGuid());
cmd.Parameters.AddWithValue("name", name);
await cmd.ExecuteNonQueryAsync();
}
//close site specific connection
await conn.CloseAsync();
}
catch (Npgsql.PostgresException ex)
{
//close site specific connection
await conn.CloseAsync();
if (ex.SqlState == "42P01") // table does not exist
{
//here admin connection string have permission to create a table
using (var adminConn = await GetAdminConnectionString())
{
try
{
//open admin specific connection
await adminConn.OpenAsync();
//create a table
using (var cmd = new NpgsqlCommand($#"CREATE Table Site_{tableId:N} (
id UUID PRIMARY KEY,
name VARCHAR(128);", adminConn))
{
cmd.ExecuteNonQuery();
}
//close admin specific connection
await adminConn.CloseAsync();
//insert data to table again
return await Insert(tableId, name);
}
catch (Exception exception)
{
//close admin specific connection
await adminConn.CloseAsync();
throw;
}
}
}
}
}
return true;
}
Question is,
Is this approach correct? without exception handing (if (ex.SqlState == "42P01") // table does not exist) do we have any better way or ant library out of the box helps here? My connection string is also different for table and admin.
Given your specific design, trying and catching 42P01 seems reasonable. An alternate approach would be to first check if the table exists (by querying information_schema.tables or pg_class), but that adds an additional roundtrip and is potentially vulnerable to race conditions.
However, consider reviewing your design of dynamically created tables. If the tables have identical schema and the goal here is to have authorization per-table, then PostgreSQL has row-level security which may allow a much better design.
Related
I am working on a Winforms c# program with a service-based database. I have created a table called Books which is empty and currently has no records.
Using some code, I am inserting into the table values. For some reason, when clicking Show Table Data, it still shows as blank despite me having added a record.
I checked to see if the record was there (but hidden) by using a DataGridView with the source of its data being the Books table. I could see the record had been created, but for some reason is not showing in the Server Explorer's Show Table Data view.
Here is my code for inserting a new record into the table:
string query = "INSERT INTO Books (ISBN, Title, Authors, Publishers, Genre, Page_Count, Quantity) VALUES (#isbn, #title, #authors, #publishers, #genre, #page_count, #quantity)";
string connectionString = "Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\LMSDB.mdf;Integrated Security=True";
// Establish connection with database
using (SqlConnection connection = new SqlConnection(connectionString))
{
// Avoid SQL injection using parameters. Replace them with their real values
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#isbn", isbn);
command.Parameters.AddWithValue("#title", title);
command.Parameters.AddWithValue("#authors", authors);
command.Parameters.AddWithValue("#publishers", publishers);
command.Parameters.AddWithValue("#genre", genre);
command.Parameters.AddWithValue("#page_count", pageCount);
command.Parameters.AddWithValue("#quantity", quantity);
try
{
connection.Open();
if (command.ExecuteNonQuery() == 1)
{
// 1 Row affected. Success
Console.WriteLine("Book added to database successfully");
ClearControlValues();
}
}
catch (Exception ex)
{
MessageBox.Show("An error occured:\n" + ex.Message);
}
finally
{
connection.Close();
}
}
As you can see, the Show Table Data view is appearing blank, despite me knowing that there is a record as I can see it on a DataGridView
Is there something I'm missing as to why the record isn't appearing?
EDIT: Solved thanks to Steve who pointed out that the Server Explorer and my code had different connection strings. Having changed these around, I now have the intended result.
I made a test with your code, and data can be inserted successfully.
As Steve said, the problem lies in your connection string.
And you can find your connectionstring by following steps:
1.Right-click on the connection name and select Properties.
2.There is an item called Connection String in the Properties window.
As follows:
Daniel Zhang
I created a sqlite database in unity... and tried to connect with this function.
void AddScores(string conn)
{
IDbConnection dbconn;
dbconn = (IDbConnection)new SqliteConnection(conn);
dbconn.Open();
using(IDbCommand dbCmd = dbconn.CreateCommand())
{
// string sqlQuery = "SELECT Id FROM PickAndPlace ";
string sqlQuery= "INSERT INTO PickAndPlace (Id) VALUES (324)";
dbCmd.CommandText = sqlQuery;
using(IDataReader reader = dbCmd.ExecuteReader())
{
while (reader.Read())
{
print(reader.GetInt32(0));
}
dbconn.Close();
reader.Close();
dbCmd.Dispose();
}
}
}
The following code not working if I try insert values...and it is showing this error "The database file is locked:
database is locked" But If I try select this works fine.So where is my mistake?
Sqlite generally accepts a single "connection". Once one application connects to the database, which means just acquiring a write lock on it, no other applications can access it for writes, but can access it for reads. Which is just the behaviour you are seeing. See File Locking And Concurrency Control in SQLite Version 3 for a bunch more details about how this works, the various locking states etc.
But in principle, you can only have a single connection open. So somehow you have more than one. Either you forget to close some connections, or multiple threads or applications are trying to modify it. Or perhaps some error occurred and left the locking files in a bad state.
I am trying to move data from some tables from one sql server database to another sql server database. I am planning to write a wcf rest service to do that. I am also trying to implement this using SQLBulkCopy. I am trying to implement the below functionality on button click. Copy Table1 data from source sql server to Table 1 in destination sql server. Same as for table 2 and table 3. I am blocked on couple of things. Is sql bulk copy a best option with wcf rest service to transfer data. I was asked not to use ssis in this task. If there is any exception while moving data from source to destination, then the destination data should be reverted back. It is something like transaction. How do I implement this transaction functionality. Any pointer would help.
Based on the info you gave, your solution would be something like the sample code I inserted. Pointers for the SQLBulkCopy: BCopyTutorial1, BCopyTutorial2 SQLBulkCopy with transaction scope examples: TrasactionBulkCopy
My version is a simplified version of these, based on your question.
Interface:
[ServiceContract]
public interface IYourBulkCopyService {
[OperationContract]
void PerformBulkCopy();
}
Implementation:
public class YourBulkCopyService : IYourBulkCopyService {
public void PerformBulkCopy() {
string sourceCs = ConfigurationManager.AppSettings["SourceCs"];
string destinationCs = ConfigurationManager.AppSettings["DestinationCs"];
// Open a sourceConnection to the AdventureWorks database.
using (SqlConnection sourceConnection = new SqlConnection(sourceCs) {
sourceConnection.Open();
// Get data from the source table as a SqlDataReader.
SqlCommand commandSourceData = new SqlCommand(
"SELECT * FROM YourSourceTable", sourceConnection);
SqlDataReader reader = commandSourceData.ExecuteReader();
//Set up the bulk copy object inside the transaction.
using (SqlConnection destinationConnection = new SqlConnection(destinationCs)) {
destinationConnection.Open();
using (SqlTransaction transaction = destinationConnection.BeginTransaction()) {
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(
destinationConnection, SqlBulkCopyOptions.KeepIdentity,
transaction)) {
bulkCopy.BatchSize = 10;
bulkCopy.DestinationTableName =
"YourDestinationTable";
// Write from the source to the destination.
try {
bulkCopy.WriteToServer(reader);
transaction.Commit();
} catch (Exception ex) {
// If any error, rollback
Console.WriteLine(ex.Message);
transaction.Rollback();
} finally {
reader.Close();
}
}
}
}
}
}
}
I have an application that saves and opens data (which is saved as SQL CE database file). Every time the project gets saved, a new .sdf file is generated with table structure defined by my code and I do not need to run any validation against it.
My concern is when user import (open) the .sdf file in a OpenFileDialog, there will be chance user may select a database file generated from a different application (i.e. having a different table schema). I would need to validate the importing database table schema or the application may crash if the wrong database file is opened and processed.
I do not need to compare schema between files. All I need is to check if the database file contain a certain table structure or table names (which I think should be sufficient for my purpose). What is the easiest way to do this?
[EDIT]
I used the following method to validate the database file, which works. I use a string array to checked against a SqlCeDataReader (which stores the Table name). It works but I wonder if there's an even easier way - is there a build in method in .NET to use?
using (SqlCeConnection conn = new SqlCeConnection(validateConnStr))
{
using (SqlCeCommand cmd = new SqlCeCommand(#"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES", conn))
{
try
{
conn.Open();
SqlCeDataReader rdr = cmd.ExecuteReader();
string[] tableArr = { "FirstTable", "SecondTable" };
int ta = 0;
while (rdr.Read())
{
if (rdr.GetString(0) != tableArr[ta])
{
isValidDbFile = false;
}
else
{
isValidDbFile = true;
}
ta++;
}
}
catch (Exception ex)
{
//MessageBox.Show(ex.ToString());
}
finally
{
conn.Close();
}
}
}
Open the database (make sure to have error handling for this, as the user can point to any file).
run: SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'MyTable'
If this returns data, your table is there.
I'm trying to create a table using SMO, and further use the SqlBulkCopy object to inject a bunch of data into that table. I can do this without using a transaction like this:-
Server server = new Server(new ServerConnection(new SqlConnection(connectionString)));
var database = server.Databases["MyDatabase"];
using (SqlConnection connection = server.ConnectionContext.SqlConnectionObject)
{
try
{
connection.Open();
Table table = new Table(database, "MyNewTable");
// --- Create the table and its columns --- //
SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(connection);
sqlBulkCopy.DestinationTableName = "MyNewTable";
sqlBulkCopy.WriteToServer(dataTable);
}
catch (Exception)
{
throw;
}
}
Basically I want to perform the above using a SqlTransaction object and committing it when the operation has been completed (Or rolling it back if it fails).
Can anyone help?
2 Things -
A - The SQLBulkCopy method is already transaction based by default. That means the copy itself is encapsulated in a transaction and works for fails as a unit.
B - The ServerConnection object has methods for StartTransaction, CommitTransaction, RollbackTransaction.
You should be able to use those methods in your code above, but I suspect if there is an issue with the table creation your try/catch will handle that appropriately.