Need help finding a good design and schema - c#

I want to make an all in one query, or even a two step query that pulls from 3 tables.
Table #1: Games - this table holds a type of game and has a description of the game etc.
Table #2: GameProfiles - this holds an id from the table 'Games', so they both have a GamesId column. This table holds Games won, Games Lost, etc etc
Table #3: Games_WhateverGame - this is not a specific table, there are multiple tables, for instance if I have a game BasketBall, there is going to be a seperate table for it called Games_BasketBall and it has custom columns depending on the game. For example, Basketball would have a column for rebounds. This table would have its own primary key id.
I can easily pull the Games and GameProfiles together using an inner join on their common "GameId" column, but how do I make it so I can also pull 'Games_BasketBall' also in the same query, dynamically depending on the GamesId. I may be structuring this wrong, so I am open to suggestions. I just cant seem to think of a really fluid way of making this work correctly because each game will have different profile entities regardless so I want to make the relations to each table easy so I can pull everything in one query.
This code has the query WITHOUT the relation on to Games_Basketball, I want to be able to pull it all into one reader so it has the information correct.
using (SqlConnection myConnection = new SqlConnection(myConnectionString))
{
myConnection.Open();
String selectSql = "SELECT * FROM aspnet_GameProfiles INNER JOIN aspnet_Games ON aspnet_GameProfiles.GameId = aspnet_Games.GameId WHERE UserId = #UserId";
SqlCommand myCommand = new SqlCommand(selectSql, myConnection);
myCommand.Parameters.AddWithValue("#GameProfile", UserId);
reader = myCommand.ExecuteReader();
gameTable.DataSource = reader;
gameTable.DataBind();
myConnection.Close();
}

For yours current shema I would go for something like:
public enum GameType { Basketball, Snooker, ... }
void BindGameData(GameType gameType)
{
[ sql connection code ]
StringBuilder sb = new StringBuilder();
// those constant parts should be stored outside in config file
sb.append("SELECT * FROM aspnet_GameProfiles INNER JOIN aspnet_Games ON aspnet_GameProfiles.GameId = aspnet_Games.GameId ");
sb.append("INNER JOIN ");
sb.append("aspnet_" + gameType.toString()); // adopt to yours naming pattern
sb.append("ON aspnet_Games.GameId = ");
sb.append("aspnet_" + gameType.toString() + ".GameId");
sb.append("WHERE UserId = #UserId");
String selectSql = sb.toString();
[rest of yours sql connection code]
}

You could also use MARS (Mulitple Active Result Sets) to accomplish your goal. In MARS you use two sqldatareader at the same time.
Here is a small sample:
SqlDataReader rdrone = null;
SqlDataReader rdrtwo = null;
string connectionstring = "server=sugandha;initial catalog = Employeedetail;uid = sa;pwd= sa";MultipleActiveResultSets = true;
SqlConnection con = new SqlConnection(connectionstring);
SqlCommand cmdone = new SqlCommand("select * from Employee", con);
SqlCommand cmdtwo = new SqlCommand("select * from Employeedept", con);
con.Open();
rdrone = cmdone.ExecuteReader();
DataTable dtone = new DataTable();
dtone.Load(rdrone);
dataGridView1.DataSource = dtone;
rdrtwo = cmdtwo.ExecuteReader();
DataTable dttwo = new DataTable();
dttwo.Load(rdrtwo);
dataGridView2.DataSource = dttwo;
con.Close();

If you want to get all the information for all games at one time, it's not practical to do that in one query. And, you know, the user doesn't want to wait for you to do, say, 135 LEFT JOIN operations to get information from all the game "whatever" tables.
So, even though it's possible, it's not practical, and you really probably don't want to do that anyway. The user isn't likely to want to read through all that data, right?
Show just what the user needs, just when the user needs it. If the user clicks on "basketball", then you can query a view that has already joined the two or three tables you need to display information about the "basketball" game.

Related

How do I go about fixing my database table after inserting a list of records different id's corresponding to same name

I insert a list of records into sql and I want the ID to correspond to only 1 specific name but in this case I want to make it so that if a name is used by 2 different ids I can forcefully override that id and change it's value to whichever ID has the lower number. I don't know if this is clear or not but I can give an example. I want it so that when the programme sees Adam also having NameID 2 it changes its ID to 1 . I am programming in c# but even pseudocode would help I am just struggling with incorporating c# logic and sql querys.
Records:
NameID:1
Name : Adam
NameID:2
Name: Adam
NameID:3
Name : Sarah
I tried to see if a primary key has been used and used a data reader to confirm if it has rows .Then I look to see if the name has also been used before with a select statement and data reader but I don't know how to go from there I am trying to understand how to query sql with csharp logic. If anyone could help me I would really appreciate it this concept has had me stuck quite a lot.
Well, we assume you have a primary key, but as suggested by others, such a opeeration would be better done in sql studio.
However, you can code this, and say somthing like this:
string strSQL =
$#"select firstName, count(*) as NameCount,
(SELECT MIN(t.Nameid) from People as T where t.Firstname = People.Firstname)
as LowNameID
FROM People
group by people.Firstname
having count(*) > 1";
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
{
using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
{
conn.Open();
DataTable rstDuplicates = new DataTable();
rstDuplicates.Load(cmdSQL.ExecuteReader());
// process duplicates, set ones with higher number = same as lowest
cmdSQL.CommandText =
#"update People Set NameID = #NameID where FirstName = #FName
AND NameID <> #NameID";
cmdSQL.Parameters.Add("#NameID", SqlDbType.Int);
cmdSQL.Parameters.Add("#FName", SqlDbType.NVarChar);
foreach (DataRow OneName in rstDuplicates.Rows)
{
cmdSQL.Parameters["#NameID"].Value = OneName["LowNameID"];
cmdSQL.Parameters["#FName"].Value = OneName["FirstName"];
cmdSQL.ExecuteNonQuery();
}
}
}
I would consider fixing up this mess. I would suggest deleting the duplicated reocrds, and setup the code to NOT allow the same name (if that is your goal here). In other words, don't allow such additions if that is your goal in the first place.

How to prevent SQL Injection in this code?

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);
}

Reading existing rows from a database using C#

The application I am developing is meant to be a quick and easy tool to import data to our main app. So the user loads in a CSV, meddles with the data a little and pushes it up to the database.
Before the data is pushed to the database, I have a verification check going on which basically says, "Does a customer exist in the database with the same name, account and sort codes? If so, put their guid (which is known already) into a list."
The problem is, the result variable is always 0; this is despite the fact that there is duplicate test data already in my database which should show a result. Added to that, using SQL Profiler, I can't see a query actually being executed against the database.
I'm sure that the ExecuteScalar() is what I should be doing, so my attention comes to the Parameters I'm adding to the SqlCommand... but I'll be blowed if I can figure it... any thoughts?
string connectionString = Generic.GetConnectionString("myDatabase");
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand check = new SqlCommand("select COUNT(*) from Customers C1 INNER JOIN CustomerBank B1 ON C1.Id = B1.CustomerId WHERE C1.Name = #Name AND B1.SortCode = #SortCode AND B1.AccountNumber = #AccountNumber", conn);
foreach (DataRow row in importedData.Rows)
{
check.Parameters.Clear();
check.Parameters.Add("#Name", SqlDbType.NVarChar).Value = row["Name"].ToString();
check.Parameters.Add("#SortCode", SqlDbType.NVarChar).Value = row["SortCode"].ToString();
check.Parameters.Add("#AccountNumber", SqlDbType.NVarChar).Value = row["AccountNumber"].ToString();
Object result = check.ExecuteScalar();
int count = (Int32)result;
if (count > 0)
{
DuplicateData.Add((Guid)row["BureauCustomerId"]);
}
}
}
Clarification: importedData is a DataTable of the user's data held in this C# application. During the ForEach loop, each row has various columns, a few of those being Name, SortCode and AccountNumber. The values seem to get set in the parameters (but will verify now).

Comparing id from user to a table asp.net

As I am new to coding I have to get an id from user and compare it to a table from students that contains a foreign key of sectionid. I would really appreciate if you help me what to do next I have searched but I'm not understanding anything.
[HttpPost]
public ActionResult CheckSectionIDagainststudentID(string sectionID)
{
int x = Int32.Parse(sectionID);
ConnectionManager connManager = new ConnectionManager();
SqlConnection conn = connManager.GetConnection();
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "Select * From Student Where sectionid = " + x;
cmd.Connection = conn;
conn.Open();
cmd.ExecuteNonQuery();
}
Although you MIGHT be close (not knowing all your tables), I would finish what you have using a SqlDataAdapter. That does a bunch of the work for you when loading into a table.
replace your "cmd.ExecuteNonQuery()" line with something like.
var sda - new SqlDataAdapter();
sda.Command = cmd;
var tbl = new DataTable();
sda.Fill( tbl );
This should pull down all records and put into a datatable object for you. Then you can go through each record and do whatever you need.
Also, fix your parameters. if expecting a number, do so. But from a web post, everything comes in as string and you need to parse as you have done. use int.TryParse() command (read up on that), to prevent crash if some bad text comes in unexpectedly.
Finally fix your query now and all future to prevent sql-injection. use place-holders and then your parameter, such as
cmd.CommandText = "Select * From Student Where sectionid = #parmSectionID";
cmd.Parameters.AddWithValue( "parmSectionID", x );
Dont add the "#" to the string representation in the parameters line.
Definitely read-up on more SQL commands throughout S/O and also SQL-Injection especially this early on in your development. Dont start with bad techniques that will bite you in the long run.

inserting data into multiple tables using a web form

i would like to know what is the standard/best way of doing the following:
i have a form web app in asp.net and using C#
the user will enter data into the form and click INSERT and it will insert data into 4 different tables.
the fields are:
primarykey, animal, street, country
the form allows for multiple animals, multiple streets and multiple countries per primarykey. so when i have data like this:
[1],[rhino,cat,dog],[luigi st, paul st], [russia,israel]
i need it inserted into tables like this:
table1:
1,rhino
1,cat
1,dog
table2:
1,luigi st
1, paul st
table3:
1,russia
1,israel
questions
I'm at a total loss on how to do this. if i just had one table and one set of data per primary key i would just use the InsertQuery and do it this way, but since it is multiple tables i don't know how to do this??
what control(s) should i use in order to allow user to input multiple values? currently i am just using textboxes and thinking of separating the entries by semi colons, but that's probably not the right way.
I wanted to recommend that you take advantage of the new multirow insert statement in SQL 2008 so that you can just pass a sql statement like this:
INSERT INTO table1(id,animal_name) values (1,cat),(1,dog),(1,horse)...
To your SqlCommand but I don't know how to build a statement like that w/o risking being victim of a SQL Injection Attack.
Another alternative is to define data table types in your sql database:
And then construct a DataTable in C# that matches your datatable type definition:
DataTable t = new DataTable();
t.Columns.Add("id");
t.Columns.Add("animal_name");
foreach(var element in your animals_list)
{
DaraRow r = t.NewRow();
r.ItemArray = new object[] { element.id, element.animal_name };
t.Rows.Add(r);
}
// Assumes connection is an open SqlConnection.
using (connection)
{
// Define the INSERT-SELECT statement.
string sqlInsert = "INSERT INTO dbo.table1 (id, animal_name) SELECT nc.id, nc.animal_name FROM #animals AS nc;"
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#animals", t);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.AnimalTable";
// Execute the command.
insertCommand.ExecuteNonQuery();
}
Read more here.
Or if you are familiar with Stored Procedures, same as previous suggestion but having the stored procedure receive the DataTable t as parameter.
If none of the above work for you, create a SqlTranscation from the Connection object and iterate through each row of each data set inserting the record in the appropriate table and finally commit the transaction. Example here.
Use Checkboxes on the front end. Have a service/repository to save the user data. Something like the following:
public void UpdateUserAnimals(Guid userId, string[] animals)
{
using (SqlConnection conn = new SqlConnection("connectionstring..."))
{
using (SqlCommand cmd = new SqlCommand("Insert Into UserAnimals(UserId, Animals) values (#UserId, #Animal)"))
{
conn.Open();
cmd.Parameters.AddWithValue("#UserId", userId);
foreach(string animal in animals)
{
cmd.Parameters.AddWithValue("#Animal", animal);
cmd.ExecuteNonQuery();
}
}
}
}
There are more complex solutions, but this is a simple one.

Categories

Resources