I'm trying to create a simple WPF application. I would like the application to indicate if it is not connected to the database, so that it cannot send requests and perform a check of its connection. Simply throw and catch the exception, which will be printed on the screen
My IsConnected method:
public static bool IsConnected(SqlConnection conn)
{
bool isConnected = false;
try
{
if (conn == null)
{
throw new ConnectionException("It is not possible to connect to the database. Please check your settings and try again");
}
else
{
isConnected = true;
}
}
catch (ConnectionException ex)
{
MessageBox.Show(ex.Message);
}
return isConnected;
}
Where I am using this IsConnected() method:
public User UserLogin(string email, string password)
{
query = #"SELECT * FROM [User] WHERE email = #email AND password = #password";`
User user = null;
try
{
using (SqlConnection conn = new SqlConnection(DatabaseSingleton.connString))
{
if (DatabaseSingleton.IsConnected(conn))
{
using (SqlCommand command = new SqlCommand(query, conn))
{
conn.Open();
command.Parameters.Add("#email", SqlDbType.VarChar, 50).Value = email;
command.Parameters.Add("#password", SqlDbType.VarChar, 50).Value = password;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
user = new User
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Second_name = reader.GetString(2),
Email = reader.GetString(3),
Password = reader.GetString(4),
User_type = (Type_Of_User)Enum.ToObject(typeof(Type_Of_User), reader.GetInt32(5))
};
}
reader.Close();
}
}
}
}
catch (InvalidInput e)
{
MessageBox.Show(e.Message);
}
return user;
}
Your approach is inherently sub optimal due to the way database access works. ADO should in any case throw an error if it times out.
When you try and connect with
SqlConnection conn = new SqlConnection
That will try and connect for a default period before it times out. I think that's 30 seconds. You may over ride that in your connection string.
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectiontimeout?view=dotnet-plat-ext-7.0
You could initially set that to say 2 seconds and reduce the wait. That's still 2 seconds though and maybe your database will be a bit slow one day.
If a database server may or may not be available then you can first try using ping to see if the server is there and working.
Using ping in c#
When you try and do anything with a database there will be a latency. You send a request off. The dbms receives it. goes and gets your data and returns it.
Most of the latency is usually down to things happening on the database server.
You should free up your ui thread by doing all database access on another thread and returning the results. You should use async methods for all db access including opening the database.
eg
OpenAsync
https://learn.microsoft.com/en-us/dotnet/api/system.data.common.dbconnection.openasync?view=net-7.0
Rather than voids use async Task for your database access.
https://www.pluralsight.com/guides/using-task-run-async-await
Related
I'm wanting to make a small and simple mobile app for a school project, I know connecting to a db from a phone is not good for security reasons but basically only I will touch it.
So to connect my Xamarin app to Mysql I downloaded the extension MysqlConnector (https://www.nuget.org/packages/MySqlConnector/2.1.8?_src=template)
Everything seemed to work at first, but now I think that there is a problem in their library that is not compatible with Xamarin:
I seem to always get a nullreference exception at the second query at line
reader = cmd.ExecuteReader();. I don't know why, nothing is null, I've printed everything.
(I've put a comment on the line where it happens) I seriously doubt it is a problem in their library since they have 37.2M downloads in total. But maybe it is just a compatability conflict, but that makes it odd that the first query works then.
Here is all my current code:
using PuppyChinoBestelling.Views;
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
using MySqlConnector;
using System.Threading.Tasks;
namespace PuppyChinoBestelling.ViewModels
{
public class LoginViewModel : BaseViewModel
{
public Command LoginCommand { get; }
public string Mail { get; set; }
public string Pass { get; set; }
public LoginViewModel()
{
Pass = string.Empty;
Mail = string.Empty;
LoginCommand = new Command(OnLoginClicked);
}
private async void OnLoginClicked(object obj)
{
MySqlConnection conn = new MySqlConnection("private");
try
{
conn.Open();
Console.WriteLine("Conn opened!");
}
catch(Exception ex)
{
Console.WriteLine("Error " + ex.Message);
}
string sql = #"SELECT * FROM users WHERE email = #email;";
var cmd = conn.CreateCommand();
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("#email", Mail);
var reader = cmd.ExecuteReader();
if (reader.HasRows)
{
sql = #"SELECT * FROM users WHERE email = #email;";
cmd = conn.CreateCommand();
cmd.Parameters.Clear();
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("#email", Mail);
reader = cmd.ExecuteReader(); //null reference happening here idk why
string pwdHashed = reader.GetString(5);
bool validPwd = BCrypt.Net.BCrypt.Verify(Pass, pwdHashed);
conn.Close();
if (validPwd)
{
await Shell.Current.GoToAsync($"//{nameof(AboutPage)}");
}
else
{
Console.WriteLine("Foute logingegevens!");
}
}
else
{
Console.WriteLine("Je bestaat niet!");
}
}
}
}
Thanks in advance!
It's hard to say for certain, but it's likely the issue is because you are not closing the reader and command, and you can't have multiple commands on the same connection.
Also, you need to advance the reader using reader.Read.
In any case there is no need to run the command twice in the first place. You already had all the information on the first run.
You also need to dispose everything with using. This automatically closes the connection.
Don't SELECT *, just select the columns you need.
Ideally, you would calculate the hash for the given password, and send it to the database server to check, rather than pulling out the real password hash from the database (could be a security risk).
Don't store hashes as strings. Instead store them as binary with the varbinary data type, and cast to byte[] on the C# side.
Unclear why you are handling errors only for opening the connection, not for executing the command.
private async void OnLoginClicked(object obj)
{
const string sql = #"
SELECT Pass
FROM users
WHERE email = #email;
";
using (var conn = new MySqlConnection("private"))
using (var cmd = new MySqlCommand(sql, conn))
{
try
{
conn.Open();
Console.WriteLine("Conn opened!");
}
catch(Exception ex)
{
Console.WriteLine("Error " + ex.Message);
return; // no point continuing
}
cmd.Parameters.AddWithValue("#email", Mail);
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
{
Console.WriteLine("Je bestaat niet!");
return; // no point continuing
}
string pwdHashed = (string)reader["Pass"];
conn.Close();
bool validPwd = BCrypt.Net.BCrypt.Verify(Pass, pwdHashed);
if (validPwd)
{
await Shell.Current.GoToAsync($"//{nameof(AboutPage)}");
}
else
{
Console.WriteLine("Foute logingegevens!");
}
}
}
}
An alternative method is to remove the reader altogether and use ExecuteScalar
private async void OnLoginClicked(object obj)
{
const string sql = #"
SELECT Pass
FROM users
WHERE email = #email;
";
using (var conn = new MySqlConnection("private"))
using (var cmd = new MySqlCommand(sql, conn))
{
try
{
conn.Open();
Console.WriteLine("Conn opened!");
}
catch(Exception ex)
{
Console.WriteLine("Error " + ex.Message);
return; // no point continuing
}
cmd.Parameters.AddWithValue("#email", Mail);
string pwdHashed = cmd.ExecuteScalar() as string;
conn.Close();
if (pwdHashed is null)
{
Console.WriteLine("Je bestaat niet!");
return; // no point continuing
}
bool validPwd = BCrypt.Net.BCrypt.Verify(Pass, pwdHashed);
if (validPwd)
{
await Shell.Current.GoToAsync($"//{nameof(AboutPage)}");
}
else
{
Console.WriteLine("Foute logingegevens!");
}
}
}
I have to make automatic generate new AccountID on my load windows form app.
So for example when users start windows form "Add new Account" in textbox for "Account id" I have to show latest value from database. If i have two accounts in database on windows form in textbox value will be three.
My code perfectly work if i have at least one account in database, but when my database is empty i got exception.
This is my code:
public int GetLatestAccountID()
{
try
{
command.CommandText = "select Max(AccountID)as maxID from Account";
command.CommandType = CommandType.Text;
connection.Open();
OleDbDataReader reader= command.ExecuteReader();
if (reader.Read())
{
int valueID = Convert.ToInt32(reader["maxID"]);
return valueID + 1;
}
return 1;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (connection!= null)
{
connection.Close();
}
}
}
Also I find answer on stackoverflow:
object aa = DBNull.Value;
int valueID = (aa as int?).GetValueOrDefault();
But this line of code works if my database is empty, but when I have one account in the database, it will always show on my windows form in account id textbox value one. I use Microsoft Access 2007 database.
I appreciate any help.
You may further simplify it like below,
Select isnull(max(accountID),0) as maxID from Account
I'm guessing you want:
public int GetLatestAccountID(string connectionString)
{
using(var dbConn = new OleDbConnection(connectionString))
{
dbConn.Open();
string query = "select Max(AccountID) from Account";
using(var dbCommand = new OleDbCommand(query, dbConn))
{
var value = dbCommand.ExecuteScalar();
if ((value != null) && (value != DBNull.Value))
return Convert.ToInt32(value) + 1;
return 1;
}
}
}
It looks like you're opening your database connection once and leaving it open during your entire program. Don't do that; that leads to race conditions and data corruption. .NET implements database connection pooling so you're not improving performance at all by leaving connections open.
You're also not telling us what you're using GetLatestAccountID for. If you're trying to use that as a primary key you are also going to run into problems with race conditions. If you want a primary key you should let the database create it and return the value after you've created the record.
public int GetLatestAccountID()
{
try
{
int accounts = 0;
command.CommandText = "select Max(AccountID)as maxID from Account";
command.CommandType = CommandType.Text;
connection.Open();
OleDbDataReader reader= command.ExecuteReader();
if (reader.Read())
{
accounts = Convert.ToInt32(reader["maxID"]) + 1;
}
return accounts;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (connection!= null)
{
connection.Close();
}
}
}
Could you use SELECT COUNT(column_name) FROM table_name; to count number of accounts instead of selecting which one is the biggest?
I want to get a specific value out of my Database.
To do so I wrote a function that I call like that:
strUserId = SqlSelectToString("SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName");
The function works like that:
private string SqlSelectToString(string strSqlCommand)
{
string result = "";
using (SqlConnection connection = new SqlConnection(strConnectionString))
{
using (SqlCommand command = new SqlCommand(strSqlCommand, connection))
{
command.Parameters.AddWithValue("#UserName", strUserName);
try
{
connection.Open();
result = (string)command.ExecuteScalar();
}
catch
{
if (connection.State == ConnectionState.Open)
{
connection.Close();
}
}
}
}
return result;
I tried many different approaches but wasn't able to add "strUserName" to the query.
The query-string remained always "SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName".
What am I doing wrong?
Thanks in advance.
EDIT:
The Username of whom we need the UserId is always the same and defined by the Webserver-Admin through the Web.config as follows:
<appSettings>
<add key="UserName" value="kunde"/>
<add key="ApplicationName" value="InternalTest"/>
before the method SqlSelectToString is executed I fetch the strUsername from the config like that:
strUserName = System.Configuration.ConfigurationManager.AppSettings["UserName"].ToString();
Hope that helps.
EDIT:
Finally found a solution to the problem.
This is the right approach:
private const string strSelectUserId = "SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName";
/// <summary>
/// Saves the result of an SQL-Query in a string
/// </summary>
/// <param name="strSqlCommand"></param>
private string SqlSelectToString(string strSqlCommand)
{
string result = "";
SqlConnection connection = new SqlConnection(strConnectionString);
SqlCommand command = new SqlCommand(strSqlCommand, connection);
command.Parameters.AddWithValue("#UserName", strUserName);
command.Parameters.AddWithValue("#ApplicationName", strApplicationName);
command.Parameters.AddWithValue("#ProjectId", strProjectId);
SqlDataReader reader;
try
{
connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
result = reader[0].ToString();
}
}
catch
{
bInsert = false;
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
return result;
}
The query-string remained always "SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName".
This is what is supposed to happen. It's the whole point of parameterized queries. The parameter value is never substituted directly into the sql command, and thus any possibility of an sql injection attack is avoided. Instead, it's more as if you executed sql code like this:
declare #UserName nvarchar(50);
set #UserName = '... magic here to populate this without actually building sql code';
SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName
(Though for Sql Server it actually relies on sp_executesql)
I'd help you more, but you never tell us what actually happens after you run the code... and this is likely because you swallow any exception that might tell you what went wrong. The code you have is almost certainly throwing an exception that would tell you exactly what's going wrong here, and you never get to see it.
Seriously, just remove the try/catch from that code entirely. You don't need it at all, and the entire catch block is just extra code.
One more thing before I go: AddWithValue() isn't the best option for your query parameters.
Put all that together, you end up with a helper method that looks more like this:
private string GetSqlScalar(string strSqlCommand, params SqlParameter[] parameters)
{
string result = "";
using (SqlConnection connection = new SqlConnection(strConnectionString))
using (SqlCommand command = new SqlCommand(strSqlCommand, connection))
{
if (parameters != null)
{
command.Parameters.AddRange(parameters);
}
connection.Open();
result = (string)command.ExecuteScalar();
}
return result;
}
And then call it like this:
strUserId = GetSqlScalar("SELECT UserId FROM BugNet.dbo.Users WHERE UserName = #UserName",
new SqlParameter("#UserName", SqlDbType.NVarChar, 50) {Value = strUserNanme });
(Of course using the correct parameter type and length from the database).
I have multiple databases (30+) that are used at clinics and setup automatically via the license software they use. So each database is named the same and use the same port, the only thing that changes is the IP. That being said, I am using the following code to attempt to run a query against them individually. However, when I change out the IP and run the script again it is returning the results from the previous server.
using System;
using System.Diagnostics;
using System.Data.OleDb;
namespace ConnectionTest
{
class Program
{
static void Main(string[] args)
{
using (OleDbConnection conn = new OleDbConnection("Provider=SAOLEDB.10;LINKS=tcpip(host=X.X.X.X,PORT=2638);ServerName=EAGLESOFT;Integrated Security = True; User ID = dba; PWD = sql"))
{
try
{
conn.Open();
using (OleDbCommand cmd = new OleDbCommand("SELECT tran_num, provider_id, tran_date FROM transactions WHERE tran_date LIKE '2015-11-23%'", conn))
{
using (OleDbDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("{0}|{1}|{2}", reader.GetValue(0).ToString(), reader.GetValue(1).ToString(), reader.GetValue(2).ToString());
}
}
}
}
catch (Exception connerr) { Debug.WriteLine(connerr.Message); }
conn.Close();
}
if (Debugger.IsAttached)
{
Console.ReadLine();
}
}
}
}
I don't see any reason why you would be getting previous results, here. Are you running multiple instances of this code at the same time? Are you manually changing the IP address in your code each time? I assume the "Transactions" object is an actual table and not something being generated on-the-fly?
With just a tiny bit of modification, you could pass the IP address as a command line parameter:
static void Main(string[] args)
{
string ip, port = null;
for (int i = 0; i < args.Length; i++)
{
if (args[i].StartsWith("/i:"))
ip = args[i].Substring(args[i].IndexOf(':') + 1);
else if (args[i].StartsWith("/p:"))
port = args[i].Substring(args[i].IndexOf(':') + 1);
}
// Default the port value to 2638 (since I have no idea if that changes).
if (string.IsNullOrEmpty(port))
port = "2638";
string connStr = string.Format("Provider=SAOLEDB.10;LINKS=tcpip(host={0},PORT={1});ServerName=EAGLESOFT;Integrated Security = True; User ID = dba; PWD = sql", ip, port);
using (OleDbConnection conn = new OleDbConnection(connStr))
{
try
{
conn.Open();
if (conn.State != System.Data.ConnectionState.Open)
// You could also implement a WHILE loop with a small delay (~1200ms) and try again to open the connection, with a counter to "fail" after a certain number (like 3) of attempts.
throw new Exception("Unable to open connection to database.");
using (OleDbCommand cmd = new OleDbCommand("SELECT tran_num, provider_id, tran_date FROM transactions WHERE tran_date LIKE '2015-11-23%'", conn))
using (OleDbDataReader reader = cmd.ExecuteReader())
while (reader.Read())
Console.WriteLine("{0}|{1}|{2}", reader.GetValue(0).ToString(), reader.GetValue(1).ToString(), reader.GetValue(2).ToString());
}
catch (Exception connerr)
{ Debug.WriteLine(connerr.Message); }
finally
{ conn.Close(); }
}
if (Debugger.IsAttached)
{
Console.ReadLine();
}
}
First and foremost (for testing purposes, that is) I would probably try removing the ServerName parameter since you're already providing an IP address explicitly.
So your connection string would be:
Provider=SAOLEDB.10;LINKS=tcpip(host=X.X.X.X,PORT=2638);Integrated Security = True; User ID = dba; PWD = sql
Similar to ORA files, DSNs, etc, it almost sounds like you have a server alias configured for EAGLESOFT that may be overwriting the IP preference in your testing.
Make sure you change EagleSoft as well as IP. You have to pass the IP address as an arg[]. Modify code as below to allow the IP to change
string host = "X.X.X.X";
string conStr = string.Format("Provider=SAOLEDB.10;LINKS=tcpip(host={0},PORT=2638);ServerName=EAGLESOFT;Integrated Security = True; User ID = dba; PWD = sql", host);
using (OleDbConnection conn = new OleDbConnection(conStr))
I've been trying to get my query to work for some time it runs but doesn't insert anything nor does it return any errors.
The database connection is open and is successfuly connection.
The Table is called errorlog and holds the following data
- id (int autoincremental, Primary key, Unique)
- exception (varchar)
- time (DateTime)
exception = String(error message)
time = DateTime.Now
Here's the code:
public void insertError(string error, DateTime time)
{
SqlCeParameter[] sqlParams = new SqlCeParameter[]
{
new SqlCeParameter("#exception", error),
new SqlCeParameter("#time", time)
};
try
{
cmd = new SqlCeCommand();
cmd.Connection = connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO errorlog (exception, time) VALUES(#exception, #time)";
cmd.Parameters.AddRange(sqlParams);
cmd.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
Any help would be appreciated, Thanks in advance.
EDIT
Removed quotes around #exception
Heres the connection:
protected DataController()
{
try
{
string appPath = System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(DataController)).CodeBase).Replace(#"file:\", "") + #"\";
string strCon = #"Data Source = " + appPath + #"Data\EasyShop.sdf";
connection = new SqlCeConnection(strCon);
}
catch (Exception e)
{
}
connection.Open();
}
Finally the way it gets called:
public bool log(string msg, bool timestamp = true)
{
DataController dc = DataController.Instance();
dc.insertError(msg, DateTime.Today);
return true;
}
Debug your application and see if connection points exactly to the
database you want. Also check if you look for the inserted records
in the same database.
If your connection belongs to the transaction, check if it's committed. You will not see those records inserted until transaction is committed.
It seems to me, that you INSERT is wrong. Remove quotes around #exception
Open SQL Server Profiler, connect to your database and check if your INSERT appears in there.