System.Data.SQLite lock/sharing conflict - c#

I'm currently using this SQLite library for my console application: http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki - which has been OK so far with SELECT queries, but doing this INSERT is causing me problems, which I have not found a solution for.
I'm guessing the code could be re-worked but I cannot see how?
Code
public string NewChannel(string _channel)
{
SQLiteConnection m_dbConnection = new SQLiteConnection(m_connection);
using (var cmd = m_dbConnection.CreateCommand())
{
m_dbConnection.Open();
cmd.CommandText = "INSERT INTO channels (name) VALUES (#name)";
cmd.Parameters.AddWithValue("#name", _channel);
try
{
int result = cmd.ExecuteNonQuery();
return "New channel added: " + _channel;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
}
Error
SQLite error (10): delayed 1375ms for lock/sharing conflict SQLite
error (14): os_win.c:34909: (5) winOpen(c:\db.sqlite-journal) - Access
is denied. SQLite error (14): os_win.c:34909: (2)
winOpen(c:\db.sqlite-journal) - The system cannot find the file
specified. SQLite error (14): cannot open file at line 34917 of
[118a3b3569] SQLite error (14): statement aborts at 7: [INSERT INTO
channels (name) VALUES (#name)]

The particular error code represents the following:
Error Code 14: SQL Lite Database File Can't be Opened.
The reason that running Visual Studio as Administrator worked is because you elevated the Permission for Read and Write. It isn't that it is actually creating a new file, but it may be Reading or Writing data to the file.
Based on particular permissions that may be restricted based on the given role, unless the permissions have been explicitly defined.
As I mentioned above the root C: requires Power User or Above in order to Write data on the root. Which when your database attempts to append, is considered Write which it may not be able to do.
Solution One: Explicitly define permission for application / user to avoid issue.
Solution Two: Write a check before it tries to access the data file to ensure proper permission.
An example of such a check:
public ValidFolderPermission(string filePath)
{
if(File.Exist(filePath))
{
// Open Stream and Read.
using (FileStream fs = File.Open(filePath, FileMode.Open))
{
byte[] data = new byte[1024];
UTF8Encoding temp = new UTF8Encoding(true);
while (fs.Read(b, 0, b.Length) > 0)
{
Console.WriteLine(temp.GetString(b));
}
}
}
}
In this case if it actually opens the file in that path, you have proper access. If it doesn't, then you don't have permission.
Important: To really test you should utilize a try and catch, even possibly boolean returns to ensure it did accurately handle before you write to the database.
You can even check the users permission level like this:
WindowsIdentity user = WindowsIdentity.GetCurrent();
WindowsPrincipal role = new WindowsPrincipal(user);
if(role.IsInRole(WindowsBuiltInRole.Administrator)
{
// Do Something
}
else
{
// Do Something Else
}
That is a generic approach, but there are better more concise ones that will test for Operating System for User Access Control features, null values for User Account, and etc. handling for other issues you may encounter.
Hopefully this points you in the proper direction though.

Related

Can't access Microsoft Access (.accdb) files with ACE OLEDB, but can access (.mdb) files with JET OLEDB

I am writing a C# executable and need to check if a given password for a Microsoft Access database is correct. It needs to be able to do this for both .mdb files and .accdb files. For .mdb I am using JET OLED and it works fine, but JET OLEDB doesn't support the newer versions of Microsoft Access so I use ACE OLEDB, but I get an error every time. Here is the relevant method:
public int CheckPassword(string password, string filePath)
{
// Ensure the correct provider for with .mdb(default) or .accdb
string providerName = "Microsoft.Jet.OLEDB.4.0";
if (Path.GetExtension(filePath) == ".accdb")
{
Console.WriteLine("Changed provider to ACE");
providerName = "Microsoft.ACE.OLEDB.12.0";
}
// Establish access to the file
var accessBuilder = new OleDbConnectionStringBuilder
{
Provider = providerName,
DataSource = filePath
};
accessBuilder["Jet OLEDB:Database Password"] = password;
// Attempt to enter a password and catch if it is incorrect
using (var conn = new OleDbConnection(accessBuilder.ConnectionString))
{
if (ConnectionState.Open != conn.State)
{
try
{
// If it fails here, likely due to an actual bad password.
conn.Open();
Console.WriteLine("0 - Success: Password Correct");
}
catch (OleDbException)
{
// Assumed bad password
Console.WriteLine("2 - Error: Password Incorrect");
return -1;
}
}
}
return 0;
}
When I give it an .accdb, the output is:
Changed provider to ACE
Unhandled Exception: System.Data.OleDb.OleDbException: Cannot open database
''. It may not be a database that your application recognizes, or the file
may be corrupt`.
When I give it an .mdb, the output is:
0 - Success: Password Correct
I've tried using a brand new Access file, but it still gives the same error
When using Ace you need to add a reference to Microsoft ActiveX Data Objects 6.1 Library and possibly Microsoft ActiveX Data Objects Recordset 6.0 Library.
I know this thread is relatively old but I found the solution to this and will post on the other similar answers to this as well for reference.

Securely Storing Database Password

I'd like to randomly generate an encryption key and password for an SQL Server CE database when it's created, and then save the key in some secure way that would allow the program to open a connection, but not be easily reachable by potential attackers.
I'm working on an offline WPF application that stores certain user and setting information in a local database.
My current implementation is to have one "Device Password" that the user sets up which is used as the encryption key for the generated SQL Server CE database password. The base64 encrypted database password is then saved in a simple .txt settings file. When the application starts up, the user enters the Device Password and that string is used as the decryption key for the saved password. If the resulting string is able to open a connection to the database, the password was correct and the program is opened with full access.
What I'm trying to do now is modify the system to allow multiple users with specific Username/Password credentials to open the program and access the database with varying levels of privilege. The way that I'm trying to achieve this is by handling the user authentication separately, and opening the database regardless of the credentials to load some basic application info.
Below is roughly my current implementation:
var candidateDBPwd = DecryptDatabasePassword(passwordBox.Password, Settings.Instance.EncryptedDatabasePassword);
if (candidateDBPwd.IsNullOrEmpty())
{
// User's password didn't decrypt database password.
return false;
}
if (File.Exists(Constants.DB_FILE))
{
// Normal operation: Try to open the database file to see that
// we've got the correct password.
string databaseArguments = Constants.DB_ARGS_SECURE + candidateDBPwd;
using (var conn = new SqlCeConnection(databaseArguments))
{
try
{
conn.Open();
}
catch (System.Data.SqlServerCe.SqlCeException ex)
{
// Failed to open the database: User's password must have been wrong!
return false;
}
}
I've spent the past few hours researching similar issues and am now beginning to wonder if it's possible. Consensus seems to state that storing passwords or connectionStrings in the App.config file is futile because if you encrypt the sections, you still need to store that key somewhere in code. Most of the existing SO threads on the issue seem to be several years out of date and it seems that that practice has deprecated. Is there some new respectable way to store a local database password? Or would you recommend a different approach to implementing the feature?
For you information here is the code snippet that can be used to encrypt certain sections of app.config. This is machine specific encryption and I think it is most simple and straightforward way to go.
I am using this with Click-once app, so that the config sections are encrypted during the first launch of the app. It means, that it is unecrypted on the publish server, it is downloaded also unencrypted and it is encrypted right after the installation finishes and application is started.
So using this method you have to distribute your files unencrypted and they are enrypted only after the installation is completed. I suppose it can be achieved by running this code during install, it depends on how you plan to install your app.
Also you can use UnprotectSection() to unencrypt previously encrypted section.
static void EncryptConfig()
{
// Encrypt config for ClickOnce deploys on first run
// ClickOnce deploys config into 2 dirs, so the parent dir is traversed to encrypt all
if (ApplicationDeployment.IsNetworkDeployed)
{
// Get paths
Assembly asm = Assembly.GetExecutingAssembly();
string exeName = Path.GetFileName(asm.Location);
string configName = exeName + ".config";
DirectoryInfo parentPath = Directory.GetParent(Directory.GetCurrentDirectory());
// Protect config files
foreach (DirectoryInfo dir in parentPath.GetDirectories())
{
foreach (FileInfo fil in dir.GetFiles())
{
if (fil.Name == configName)
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = fil.FullName;
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
ProtectSection(config, "connectionStrings");
config.Save(ConfigurationSaveMode.Modified);
}
}
}
}
}
private static void ProtectSection(Configuration config, string sectionName)
{
ConfigurationSection section = config.GetSection(sectionName);
if (section != null)
{
if (!section.SectionInformation.IsProtected)
{
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
}
section.SectionInformation.ForceSave = true;
}
else
Tools.LogWarning("Section {1} not found in {0}.",config.FilePath, sectionName);
}
You can store it in registry editor. You mention that your system is offline wpf application .

Wix - File is locked for delete after opening its database

I have some problems while trying using WindowsInstaller library or Wix Microsoft.Deployment.WindowsInstaller.
I'm, getting exception that the file being used by the process and I cannot delete it even though I've closed all record,view and database and disposed them.
try
{
string currentDir = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
string msiPath = "PathTo\MyMSI.msi";
using (InstallPackage installPackage = new InstallPackage(msiPath, DatabaseOpenMode.ReadOnly))
{
string query = "SELECT * FROM Property WHERE Property = 'ProductVersion'";
using (View view = installPackage.OpenView(query))
{
view.Execute();
using (Record record = view.Fetch())
{
string version = record.GetString(2);
Console.WriteLine(version);
record.Close();
}
view.Close();
}
installPackage.Close();
}
File.Delete(msiPath);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
And still I get the following:
Access to the path 'PathTo\MyMSI.msi' is denied.
I've also tried with the object
Database
Any help will be appreciated.
I was able to figure out what is blocking the delete action.
It appears that the file was in read only.
I don't know why I got this kind of exception but the following solved it:
//removing read only from file in order to interact with it
FileInfo fileInfo = new FileInfo(msiPath);
if (fileInfo.IsReadOnly)
{
fileInfo.IsReadOnly = false;
}
Hope it will help others.
I appreciate everyone who helped here for your time.
Below are some steps you could follow for ur problem :
Wait a minute and try deleting the file again, sometimes Windows or the program using the file may still be closing and therefore still using the file you're attempting to delete.
Close and Explorer window and re-open.
Locate the program using the file and close it. If you're uncertain what program is using the file, close all programs until you're able to delete the file.
Try using unlocker, a free software program designed to unlock any file being used by Windows or other programs without restarting the computer.
Reboot the computer. If after closing all programs you're still unable to delete the file, it's likely that something in the background is still using the file.
If after rebooting the computer you're still unable to delete the file, boot the computer into Safe Mode and delete the file.
Thanks

How to add PDF files path to SQL database and access them ? ( C# )

I have to build a software which can search and display PDF files from a folder on the local network.
My supervisor was requesting to have a database and store the path of PDF files there(not to the PDF itself because of the large amount), so I can search and open them from the software.
I would greatly appreciate if you could give me some ideas for solving it.
Haven't you already solved it? You could have an application such as a service or even a command line application that could poll/manually look into a specified folder, from there you would get the full file location and persist this into your database. When you need to search for the PDF you could perform a query against your database for PDF's that match your criteria.
You could even strip the filename from the filepath and query on that rather than the filepath and store the filepath in a different column (providing you're using a relational database).
Edit
Based on your comment on the other answer, I would stick with SQL server. From what I understand you want to automatically "catologue" the PDFs dropped in that folder, you can do this by writing a simple windows service. From that Windows service you can use an ORM (like Entity Framework) or ADO.net to persist your changes to your database. From the application you wish to display the result (be it Web app or a Win forms application, whatever), you can just query the correct column in your DB
Resources:
Entity Framework
Linq to Entities
private string[,] GetImagesFromServerFolder()
{
IntPtr token;
if (
!NativeMethods.LogonUser([Server_Login_Name], [Server_Location],
[Server_Login_Password], NativeMethods.LogonType.NewCredentials,
NativeMethods.LogonProvider.Default, out token))
{
throw new Win32Exception();
}
try
{
IntPtr tokenDuplicate;
if (!NativeMethods.DuplicateToken(
token,
NativeMethods.SecurityImpersonationLevel.Impersonation,
out tokenDuplicate))
{
throw new Win32Exception();
}
try
{
using (WindowsImpersonationContext impersonationContext =
new WindowsIdentity(tokenDuplicate).Impersonate())
{
/******************* CODE FROM HERE *******************/
List<string> files = new List<string>(Directory.GetFiles(_PHYSICAL SERVER LOCATION_));
return files;
/******************* CODE TO HERE *******************/
}
}
finally
{
if (tokenDuplicate != IntPtr.Zero)
{
if (!NativeMethods.CloseHandle(tokenDuplicate))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
}
finally
{
if (token != IntPtr.Zero)
{
if (!NativeMethods.CloseHandle(token))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
}
This will then return a list of all the .pdf files and locations
You can then sore that in a DB.
Then, run through all the files in the list (In your main running method);
List<string> file = GetImagesFromServerFolder();
foreach (var s in file)
{
const string connStr = "INSERT INTO tblPdfLocations (location) VALUES (#location)";
//Store the connection details as a string
string connstr =
String.Format(#"SERVER=[LOCATION]; UID=[USERNAME]; pwd=[PASSWORD]; Database=[DATABASE]");
//Initialise the connection to the server using the connection string.
_sqlConn = new SqlConnection(connstr);
var sqlComm = new SqlCommand(connStr, _sqlConn) {CommandType = CommandType.Text};
sqlComm.Parameters.Add(new SqlParameter("#location", SqlDbType.VarChar, 50)).Value = s;
sqlComm.ExecuteNonQuery();
_sqlConn.Close();
}
read about some ORM to work with database. For example, Entity Framework.
I would like to offer a different perspective than the other answers provided because - the storage and retrieval of such documents like PDF, Word, JPEG etc files is well into the realms of "Content Management" (CM) applications. Although relatively new in the big world of IT - these applications are mature and offer industrial strength solutions.
They use database back-ends and offer a host of facilities - the one you are interested in is the usage of CM purely as a content repository.
You don't have to look at the offerings from the big vendors (ie. Oracle Webcenter) - there are free and well supported alternatives such as Drupal and Joomla. Each of them have API's which I assume will allow you to connect into their repositories from third-party applications.
You should be looking at a solution which can grow with your company's requirements and not look to reinvent something which is already out there.

MSDTC Exception during Transaction: C#

I am getting a MSDTC exception in a Transaction in a C# application. The functionality is to upload one lakh (one hundred thousand) zipcode records into database tables after reading from a csv file. This operation is done in around 20 batch database operations (each batch containing 5000 records). The functionality is working fine, if I don’t use transaction.
The interesting part is that other functionalities that uses transactions are able to complete their transactions. This leads me to an assumption that the exception message is a misleading one.
Any thoughts on what could be the issue?
Exception: “Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool.”
Source: System.Transactions
Inner Exception: “The transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D024)”
Note: There is a for loop inside the transaction. Is it causing any issue?
The actual requirement is: There are some existing zipcodes in zipcode table. Each month the administrator will upload the new zipcode csv file. The new items from csv get inserted. Zipcodes which are not available in csv (but present in database) are considered to be retired and is to be deleted. The list of retired zip codes is to be returned to the User Interface. The newly added zip codes also need to be returned.
private void ProcessZipCodes(StringBuilder dataStringToProcess, int UserID)
{
int CountOfUnchangedZipCode = 0;
string strRetiredZipCode = "";
string strNewZipCode = "";
dataStringToProcess.Remove(dataStringToProcess.Length - 1, 1);
if (dataStringToProcess.Length > 0)
{
List<string> batchDataStringList = GetDataStringInBatches(dataStringToProcess);
//TimeSpan.FromMinutes(0) - to make transaction scope as infinite.
using (TransactionScope transaction = TransactionScopeFactory.GetTransactionScope(TimeSpan.FromMinutes(0)))
{
foreach (string dataString in batchDataStringList)
{
PerformDatabaseOperation(dataString, UserID);
}
transaction.Complete();
}
}
}
private List<string> GetDataStringInBatches(StringBuilder dataStringToProcess)
{
List<string> batchDataStringList = new List<string>();
int loopCounter = 0;
string currentBatchString = string.Empty;
int numberOfRecordsinBacth = 5000;
int sizeOfTheBatch = 0;
List<string> individualEntriesList = new List<string>();
string dataString = string.Empty;
if (dataStringToProcess != null)
{
dataString = dataStringToProcess.ToString();
}
individualEntriesList.AddRange(dataString.Split(new char[] { '|' }));
for (loopCounter = 0; loopCounter < individualEntriesList.Count; loopCounter++)
{
if (String.IsNullOrEmpty(currentBatchString))
{
currentBatchString = System.Convert.ToString(individualEntriesList[loopCounter]);
}
else
{
currentBatchString = currentBatchString+"|"+System.Convert.ToString(individualEntriesList[loopCounter]);
}
sizeOfTheBatch = sizeOfTheBatch + 1;
if (sizeOfTheBatch == numberOfRecordsinBacth)
{
batchDataStringList.Add(currentBatchString);
sizeOfTheBatch = 0;
currentBatchString = String.Empty;
}
}
return batchDataStringList;
}
private void PerformDatabaseOperation(string dataStringToProcess, int UserID)
{
SqlConnection mySqlConnection = new SqlConnection("data source=myServer;initial catalog=myDB; Integrated Security=SSPI; Connection Timeout=0");
SqlCommand mySqlCommand = new SqlCommand("aspInsertUSAZipCode", mySqlConnection);
mySqlCommand.CommandType = CommandType.StoredProcedure;
mySqlCommand.Parameters.Add("#DataRows", dataStringToProcess.ToString());
mySqlCommand.Parameters.Add("#currDate", DateTime.Now);
mySqlCommand.Parameters.Add("#userID", UserID);
mySqlCommand.Parameters.Add("#CountOfUnchangedZipCode", 1000);
mySqlCommand.CommandTimeout = 0;
mySqlConnection.Open();
int numberOfRows = mySqlCommand.ExecuteNonQuery();
}
Dev Env: Visual Studion 2005
Framework: .Net 3.0
DB: SQL Server 2005
When I run the query SELECT [Size],Max_Size,Data_Space_Id,[File_Id],Type_Desc,[Name] FROM MyDB.sys.database_files WHERE data_space_id = 0 --It says the size (of log) is 128
UPDATE
We have three different databases used in our application. One for data, one for history and one for logging. When I put enlist = false in the above connectionstring, for the time being, it is working. But it is in my development environment. I am skeptic about whether it will work in production also. Any thought on potential risks?
Thanks
Lijo
When you are opening more than one connection within a TransactionScope, the running transaction will automatically be escalated to a distributed transaction. For distributed transactions to work, the MSDTC on both SQL Server and the machine running the application must be configured to allow network access. SQL Server and the local DTC communicate when running distributed transactions.
The problem in your case is most likely that MSDTC on the machine running your application does not allow network access because this is the default for workstations. To fix this do the following:
Go to "Control Panel" -> "Aministration" -> "Component Services".
Browse through the tree until you get to a node called "Local DTC" or something like that.
Right-click and choose "Properties".
Go to "Security" and make sure that you allow network access and also allow inbound and outbound communication with DTC.
Click "Ok".
You will probably be prompted to restart DTC. There seems to be a bug in the UI because even though you accept a restart of the DTC, it will not be restarted. Instead you have to restart the DTC service manually in the service manager.
BTW, remember to close the connection after use in PerformDatabaseOperation. It is good practice to put it in a using block:
using (SqlConnection mySqlConnection = new .....)
{
// Some code here...
mySqlConnection.Open();
// Some more code ...
}
Is it possible that aspInsertUSAZipCode interacts with a linked server? If it does then it will try and promote your current local transaction to a distributed transaction (with transactional integrity preserved between your server and the linked server).
It may be necessary to do this outside of a transaction, if the MSDTC on the remote server cannot be configured for distributed transactions. I think the best way to do this is to create a temporary table and then SqlBulkCopy your records into it and then execute aspInsertUSAZipCode using the temporary table on the server. You may need to use a cursor.
The purpose of the temporary table is so that if something goes wrong, the table is removed at the termination of your connection.
You are probably hitting some max amount of data in a transaction limit.
Check your event log for any msdtc errors http://technet.microsoft.com/en-us/library/cc774415(WS.10).aspx
Having 100,000 rows in a single transaction is going to give you problems.
I do not think that a single transaction is going to work in this case, you need to look at why you are using a transaction here, and find a different way to accomplish it.

Categories