I am exploring Silverlight (C#) and SQLServer as a next evolution for our current (slow) Access database. So far everything has been great, using DomainServices to retrieve the data I need. In our database we have a table (Supervisors) with Supervisor_ID, Supervisor_FirstName, Supervisor_LastName and many other fields.
What I want to do is recreate a function I use in my current database called EntityNameFirstLast(EntityID) which would take an integer. I could then retrieve the value of [Supervisor_FirstName] from [Supervisors] table where [Supervisor_ID] == EntityID using the following:
FirstName = DLookup("[Supervisor_FirstName]", "Supervisors", "[Supervisor_ID] = EntityID
I would do the same for lastname and combine the strings returning one string with First and last name.
How can I get just a single value from my database through my DomainService (or any way for that matter)? I understand that IQueryable GetSupervisorByID(Int SupID) will return the entire row that I need, but how can I get a specific field from that row?
I am also aware that I can set the DomainDataSource in my XAML and then bind to the data I want, but I am curious if what I asked above is doable or not.
There are number of ways you can accomplish your requirement if what you need is a single value from MS-SQL server:
1.Use a Query to do the concatenation and then use its output in your code
Select Supervisor_FirstName + ' ' + Supervisor_LastName as Supervisor_FullName From Supervisors Where Supervisor_ID = EntityID
Now you can get the above query to execute through a SqlCommand and get the part thats interesting to you
private string GetSupervisorFullName(string entityID, string connectionString) {
string query = "Select Supervisor_FirstName + ' ' + Supervisor_LastName as Supervisor_FullName From Supervisors Where Supervisor_ID = #EntityID";
string supervisorFullname = "";
using(SqlConnection con = new SqlConnection(connectionString)) {
SqlCommand cmdSupervisorFullname = new SqlCommand();
cmdSupervisorFullname.Connection = con;
cmdSupervisorFullname.CommandText = query;
cmdSupervisorFullname.CommandType = CommandType.Text;
SqlParameter paraEntityID = new SqlParameter();
paraEntityID.ParameterName = "#EntityID";
paraEntityID.SqlDbType = SqlDbType.NVarChar;
paraEntityID.Direction = ParameterDirection.Input;
paraEntityID.Value = entityID;
cmdSupervisorFullname.Parameters.Add(paraEntityID);
try {
con.Open();
supervisorFullname = (String) cmdSupervisorFullname.ExecuteScalar();
} catch(Exception ex) {
Console.WriteLine(ex.Message);
}
return supervisorFullname;
}
}
2.Second way would be create a Scalar function in the SQL for your requirement and then access that function using the same kind of method as mentioned above.
Then finally you would take the return value from your method GetSupervisorFullName and populate any control value of your choice.
Please do note that there are again other methods of doing the same with LINQtoSQL or with any other ORM tools. The above 2 methods are the basic way of accomplishing them.
Hope that helps.
Related
How can i prevent these code of getting SQL injected? It's a login system that i'm learning. Here's the code!
if (!(string.IsNullOrWhiteSpace(textBox1.Text)) && !(string.IsNullOrWhiteSpace(textBox2.Text)))
{
MySqlConnection mcon = new MySqlConnection("datasource = 127.0.0.1; port = 3306; username = root; password = ; database = rpgmaster;");
mcon.Open();
DataTable table = new DataTable();
MySqlDataAdapter adapter = new MySqlDataAdapter("Select * From users where Username = '" + textBox2.Text + "' and password = '" + textBox1.Text + "'", mcon);
adapter.Fill(table);
if (table.Rows.Count <= 0)
{
MessageBox.Show("Você não está registrado!");
}
else
{
MessageBox.Show("Logado com sucesso! ");
}
mcon.Close();
}
Thanks for the help! Really appreciate it!
If you're learning, you could perhaps move on from this old low level way of doing data access and use something a bit more modern and easy. Dapper is an example of a library that isn't a huge leap above what you already know but makes your life a lot nicer:
using(var conn = new MySqlConnection("conn str here"){
var sql = "SELECT count(*) FROM tblUsers WHERE username = #u AND password = #p";
var prm = new {
u = txtUsername.Text, //give your textboxes better names than textbox2,textbox1!
p = txtPassword.Text.GetHashCode() //do NOT store plain text passwords!
};
bool valid = await conn.QuerySingleAsync<int>(sql, prm) > 0;
if(valid)
... valid login code
else
... invalid login
}
Some notes on this:
dapper is a device that you simply give your sql and parameter values to
the sql holds #parameters names like #u
an anonymous typed object has properties called the same name as the parameter name, with a value, like u = "my username"
use async/await when running queries; dapper makes this easy. Avoid jamming your UI up on queries that take 10 seconds to run
in this case you only need to ask the db to count the matching records, you don't need to download them all to find out if there are any, so we use QuerySingleAsync<int> which queries a single value of type it, and if it's more than 0, the login was valid
never store password in a database in plaintext. Use a one way hashing function like MD5, SHA256 etc, even the lowly string.GetHashCode is better than storing plaintext, particularly because people use the same passwords all the time so anyone breaking into your db (very easy; the password is in the code) will reveal passwords treat people probably use in their banking etc. We can't really be asking, on the one hand, how to prevent a huge security hole like SQL injection, and then on the other hand leave a huge security hole like plaintext passwords ;)
always name your textboxes a better name than the default textboxX - it takes seconds and makes your code understandable. If Microsoft called all their class property names like that, then the entire framework would be full of things like myString.Int1 rather than myString.Length and it would be completely unusable
life is too short to spend it writing AddWithValue statements; use Dapper, Entity Framework, strongly typed datasets.. Some db management technology that eases the burden of writing that code
Where Dapper makes things really nice for you is its ability to turn objects into queries and vice versa; this above is just a basic count example, but suppose you had a User class:
class User
{
string Name { get; set; }
string HashedPassword { get; set; }
int age {get; set; }
}
And you had a table tblUsers that was similar (column names the same as the property names), then you could query like:
User u = new User() { Name = "someuser" };
User t = await conn.QuerySingleAsync<User>("SELECT Name, HashedPassword, Age FROM tblUsers WHERE Name = #Name", u);
We want to look up all the info of the someuser user, so we make a new User with that Name set (we could also use anonymous type, like the previous example) and nothing else, and we pass that as the parameters argument. Dapper will see the query contains #Name, pull the contents of the Name from the u user that we passed in, and run the query. When the results return it will create a User instance for us, fully populated with all the data from the query
To do this old way we'd have to:
have a command,
have a connection,
add parameters and values,
open the connection,
run the sql,
get a reader,
check if the reader had rows,
loop over the reader pulling the first row,
make a new User,
use reader.GetInt/GetString etc to pull the column values out one by one and
finally return the new user
oh and dispose of all the db stuff, close the connection etc
Writing that code is repetitive, and it is really boring. In computing, when we have something repetitive and boring, that we need to do thousands of times through out life (like serializing to json, calling a webservice, designing a windows UI) we find some way to make the computer do the repetitive boring bit; they do it faster and more accurately than we can. This is exactly what Dapper does; it does away with that boring repetitive and reduces it to a single line where you say what you want back, using what query, with what parameters. And it keeps your UI working:
await x.QueryAsync<type>(query, parameters)
Win. Seek out some Dapper tutorials! (I have no affiliation)
Try using parameters please see updated sample of your code below:
if (!(string.IsNullOrWhiteSpace(textBox1.Text)) && !(string.IsNullOrWhiteSpace(textBox2.Text)))
{
using (MySqlConnection mcon = new MySqlConnection("datasource = 127.0.0.1; port = 3306; username = root; password = ; database = rpgmaster;"))
{
mcon.Open();
MySqlCommand cmd = new MySqlCommand("Select * from users where username=?username and password=?password", mcon);
cmd.Parameters.Add(new MySqlParameter("username", textBox2.Text));
cmd.Parameters.Add(new MySqlParameter("password", textBox1.Text));
MySqlDataReader dr = cmd.ExecuteReader();
if (dr.HasRows == true)
{
MessageBox.Show("Você não está registrado!");
}
else
{
MessageBox.Show("Logado com sucesso! ");
}
}
}
Use parameters to pass and check their length, Use stored procedure instead of a query in the code. Use columns instead of * in Select. And please make sure you don't store the plain password in the DB
Use Parameters
using (MySqlConnection mcon = new MySqlConnection(connectionString))
{
string commandText = "SELECT * FROM users WHERE Username = '#tbxText'"
SqlCommand command = new SqlCommand(commandText, mcon);
command.Parameters.AddWithValue("#tbxText", textBox2.Text);
}
I am stuck at one problem and I just can't solve this.
I get this Error:
Error Message
That's the relevant table
The Code:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = " + #departmentCB.Text;
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#nextMaintainance", nextMaintainanceDT.Value);
command.ExecuteNonQuery();
The weird thing I don't understand is that a similar code works just fine without any error in my project:
query = "UPDATE LDV SET received = #received, department = #department WHERE Id =" + #idTxt.Text;
command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#received", inDT.Value);
command.Parameters.AddWithValue("#department", departmentCb.Text);
command.ExecuteNonQuery();
MessageBox.Show("Lungenautomat wurde aktualisiert");
If relevant, my connection string:
connectionString = ConfigurationManager.ConnectionStrings["SCBA_Manager_0._1.Properties.Settings.SCBAmanagerConnectionString"].ConnectionString;
I really hope you can help me :(
Thank you!
The department column is a text column, so comparing it to a value means the value should be wrapped in quotes.
// This fix is not the recommended approach, see the explanation after this code block
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = '" + departmentCB.Text + "'";
// ^--------------------------^------ single quote added to wrap the value returned by departmentCB.Text
On the other hand, this error does not occur in your second example, because there you're correctly using the Parameters.AddWithValue() method to add the value for the #department parameter, and because id is a numeric column, so it doesn't require the value wrapped in quotes.
However, while the code shown above does the job, it is not the right way of doing the job. The correct way is to used parameters for all values to be injected into a query. The queries you've shown above are already correctly using parameters for some values (e.g. nextMaintenance in the first query, received and department in the second), but are incorrectly doing string concatenation for other values (e.g. department in the first query, id in the second).
Usage of Parameterized SQL
The benefit of using parameterized SQL is that it automatically takes care of adding quotes, prevents SQL injection, etc.
Therefore, its best to change your first code block to:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = #department";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#department", departmentCb.Text);
command.Parameters.AddWithValue("#nextMaintainance", nextMaintainanceDT.Value);
command.ExecuteNonQuery();
Notice how the string query is a single string without any messy concatenation, and that it contains two parameters #nextMaintenance and #department? And how the values for those parameters are correctly injected using Parameters.AddWithValue() in the following lines?
Your second code block can be similarly improved by using a parameter for the Id column.
query = "UPDATE LDV SET received = #received, department = #department WHERE Id = #Id ";
command.Parameters.AddWithValue("#Id", idTxt.Text);
Further Information
Do read up about SQL injection ( https://technet.microsoft.com/en-us/library/ms161953(v=sql.105).aspx ) to see how using string concatenation like your original code can lead to various security issues, and why parameterized queries are the preferred way of injecting dynamic values into SQL queries.
You can read up more about parameterized queries here: https://msdn.microsoft.com/en-us/library/yy6y35y8(v=vs.110).aspx
In your first example, the WHERE clause evaluates to
WHERE department = Kasseedorf
wheras it should be
WHERE department = 'Kasseedorf'
So the line should be
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = '" + #departmentCB.Text +"'";
It works in the second example, because id is an integer and doesn't neet quotes.
I have a requirement where I need to read queries from Access DB in c# and check if the access db query has any keyword like "KEY" if it has keywords I need to enclose that in square brackets"[]".just like how it is done in SQL.
Could someone suggest me how to do that?
You can retrieve the query text like this:
string connString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\...\myDB.mdb";
using (var conn = new OleDbConnection(connString )) {
conn.Open();
string[] restrictions = new string[] { null, null, "myQuery" };
DataTable schema = conn.GetSchema("Views", restrictions);
if (schema.Rows.Count > 0) {
DataRow row = schema.Rows[0];
string queryText = (string)row["VIEW_DEFINITION"];
Console.WriteLine(queryText);
}
}
If you drop the restrictions argument with the query name, conn.GetSchema("Views") returns one row for each query. If you query conn.GetSchema("Procedures") other types of queries like insert, update and DDL statements that are not considered as queries are returned in row["PROCEDURE_DEFINITION"].
View (query) names are returned in row["TABLE_NAME"] and procedure names in row["PROCEDURE_NAME"].
And you can update the query like this:
using (var conn = new OleDbConnection(connString)) {
conn.Open();
var cmd = new OleDbCommand("DROP PROCEDURE myQuery", conn);
cmd.ExecuteNonQuery();
cmd = new OleDbCommand("CREATE PROCEDURE myQuery AS SELECT * FROM myTable", conn);
cmd.ExecuteNonQuery();
}
Strangely enough the OleDb CREATE DDL (Data Definition Language) designates the queries as 'procedures' but the schema table returns a 'VIEW_DEFINITION' and the query name is returned in the column 'TABLE_NAME'. SELECT queries must be retrieved as "Views", other types of queries as "Procedures"; however, both types are created as PROCEDUREs.
While I was testing the answer that #Olivier Jacot-Descombes provided, I was not able to retreive all the queries text representation. Therefore I applied some other method where you open the existing Ms Access database instance and read the queries that are stored in it.
Here is the class I used:
public class MsAccess
{
private Microsoft.Office.Interop.Access._Application _oAccess;
public MsAccess(string path)
{
_oAccess = (Microsoft.Office.Interop.Access._Application)System.Runtime.InteropServices.Marshal.BindToMoniker(path);
}
public string ReturnSqlQueryText(string queryName)
{
string queryDef = null;
var qdefs = _oAccess.CurrentDb().QueryDefs;
foreach (QueryDef qdef in qdefs)
{
if(qdef.Name.Equals(queryName))
queryDef = qdef.SQL;
}
return queryDef;
}
}
Using this code might require you adding using Microsoft.Office.Interop.Access.Dao and Microsoft.Office.Interop.Access both (15.0.0.0) where you can find them under Extension on the reference menu
How would I take info stored in a Select method and transfer it to a string? I'm trying to get the max value from the match_id column and get its value from command.CommandText into the matchCode string. Where would I go from here?
string connectString = "Server=myServer;Database=myDB;Uid=myUser;Pwd=myPass;";
string matchCode = "";
MySqlConnection connect = new MySqlConnection(connectString);
MySqlCommand command = connect.CreateCommand();
command.CommandText = "SELECT MAX(VAL(match_id)) FROM `data`";
try
{
connect.Open();
command.ExecuteNonQuery();
matchCode = "??";
connect.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
I'm new to C#, as it's like my fourth day trying it out. Thanks for the help!
The ExecuteNonQuery() method is for INSERT/UPDATE/DELETE queries. If you're just getting a single value back, use ExecuteScalar(). If you're getting a whole result set back, use ExecuteReader() or Fill() a DataSet object.
Also, there are some things that are idiomatic to C# that you should be doing:
public int GetMatchCode()
{
//this could be loaded from config file or other source
string connectString = "Server=myServer;Database=myDB;Uid=myUser;Pwd=myPass;";
string sql = "SELECT MAX(VAL(match_id)) FROM `data`";
using (var connect = new MySqlConnection(connectString))
using (var command = new MySqlCommand(sql, connect))
{
connect.Open();
var result = command.ExecuteScalar();
if (result == DBNull.Value)
{
//what you do here depends on your application
// if it's impossible for the query to return NULL, you can even skip this
}
return (int)result;
}
}
Some of the changes need explanation:
I don't ever call .Close(). The using block takes care of that for me, even if an exception was thrown. The old code would have left the connection hanging if an exception occured.
.Net developers tend to believe in very small methods. More than that, this method ought to be part of a class that has nothing but other simple public data access methods and maybe a few private helper methods or properties for abstracting common code in the class.
There is no exception handling code here. If you have small methods that are part of a generic database access class, exception handling should be at higher level, where you are better positioned to make decisions about how to proceed.
How to properly do the following update:
using (OracleConnection conn = new OracleConnection())
using (selCmd)
{
string sql1 = "update Table1 set name = joe where id = 10;"
string sql2 = "update Table2 set country = usa where region = americas;"
string sql3 = "update Table3 set weather = sunny where state = CA;"
string sql4 = "update Table4 set engine = v8 where maker = benz;"
cmdUpdate.CommandText = sql(#);
cmdUpdate.Connection = conn;
recs = cmdUpdate.ExecuteNonQuery();
}
I am aware of all or nothing if it's a transaction but I just to see how it works with correct approach.
I'm thinking iterate an array of items [sql1,sql2,sql3,sql4] and pass sql(#) in the CommandText and perform ExecuteNonQuery each time.
If I remember correctly, it is possible to concatenate multiple SQL statements in one string separated by semi-colons (;). Otherwise, there is nothing wrong with executing multiple ExecuteNonQuery() calls.
string sql1 = "BEGIN update Table1 set name = 'joe' where id = 10;",
sql2 = "update Table2 set country = 'usa' where region = 'americas';",
sql3 = "update Table3 set weather = 'sunny' where state = 'CA';",
sql4 = "update Table4 set engine = 'v8' where maker = 'benz'; END;";
string sql = string.Format("{0}{1}{2}{3}",sql1,sql2,sql3,sql4);
using (OracleConnection conn = new OracleConnection())
using (OracleCommand cmdUpdate = new OracleCommand(sql, conn))
{
conn.Open();
recs = cmdUpdate.ExecuteNonQuery();
}
I recently came across this issue in some old code. We dynamically build chain of SQL calls (with support for Oracle and Sql Server). Since there is no current Oracle production implementation, nobody tested Oracle operation and customer bugs are not coming in. I found a code that builds chain of commands and then, for Oracle it uses String.Split(';'). Then, it uses a loop to execute each statement in transaction: rowsAffecter += ExecuteNonQuery....
I don't like this idea because without parameterization it is dangerous approach, since some data can contain ;. But even if parameterization is in place...
... one of the issues of making anonymous block for Oracle ("begin... end;") is that ExecuteNonQuery will not return number of rows (returns -1), which is sometimes needed to judge if something got updated or not.
to solve this issue I've done this
private string AppendOracleCountOrNothing(StringBuilder sql)
{
if (_myProvider == Providers.Oracle)
sql.AppendLine("rowCnt := rowCnt + SQL%ROWCOUNT;");
}
public void SomeMethod()
{
var longSqlChain = new StringBuilder(2000);
longSqlChain.Append("Insert into table...;");
AppendOracleCountOrNothing(longSqlChain);
if (someCondition)
{
longSqlChain.AppendLine("Update anotherTable...;");
AppendOracleCountOrNothing(longSqlChain);
}
// may be, add some more sql to longSqlChain here....
int rowsAffected;
if (_myProvider == Providers.Oracle)
{
longSqlChain.Insert(0, #"DECLARE
rowCnt number(10) := 0
BEGIN
").AppendLine(#":1 := rowCnt;
END;");
// Now, here we have some abstract wrappers that hide provider specific code.
// But the idea is to prepare provider specific output parameter and then parse its value
IDataParameter p = ParameterWrapper.PrepareParameter(":1", 0, ParameterDirection.Output, myProvider); // note IDataParameter
SqlExecWrapper.ExecuteNonQuery(_myProvider, CommandType.Text, sql, new[]{p});
rowsAffected = p.GetParameterValue(); // GetParameterValue is an extension on IDataParameter
}
else // sql server
{
rowsAffected = SqlExecWrapper.ExecuteNonQuery(_myProvider, CommandType.Text, sql, null);
}
}
This way we make one trip to DB and get the return number of rows affected by this call. and queries can be parameterized as well. Again, better to develop abstraction layer, so, you can call something like parameterizer.CreateParameter(10), which will add parameter to collection and generate :1, :2, :3, etc. (oracle) and #1, #2, #3, etc. (sql server), in your sql statement.
Another approach is to create a simple extension method (ExecuteMultipleNonQuery) that simply splits the string on all semicolons and executes each statement in a loop:
public static class DbCommandExtensions {
public static void ExecuteMultipleNonQuery(this IDbCommand dbCommand)
{
var sqlStatementArray = dbCommand.CommandText.Split(new string[] {";"}, StringSplitOptions.RemoveEmptyEntries);
foreach (string sqlStatement in sqlStatementArray)
{
dbCommand.CommandText = sqlStatement;
dbCommand.ExecuteNonQuery();
}
}
}