Diskuze: Vložení dat do tabulky
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.


Do ID se dává NULL nebo ho vynecháš.
Vidím tam SQL injection. Pokud jsou vstupní data od uživatele, tak riskuješ.
Fungují tak i sloupce typu text? Myslím si, že ne, protože hodnoty musí být uzavřeny v apostrofech.
Obojí řeší parametrizované dotazy. Používej je. Vypadá to i lépe než slepování SQL dotazu z řetězců.
matesax:6.10.2012 8:46
Null jsem dával jako první:
SetDataToDatabase(new object[] { null, "Sam", "George", DateTime.Now, "Georgia", "Gradec Králové", "Bieblova", 13, 50003, "Bieblova 342, Hradec Králové 13", 732958886, 0, "[email protected]" });
Hodí:
The column name is not valid. [ Node name (if any) = ,Column name = @id ]
Data hlídám při vstupu (respektive pevně je rozděluji) - takže v tom problém nevidím - ale chtěl bych ještě umět udělat něco jako testovací řádek - přidat jej, jen když bude validní...
Píši, že tomu se chci vyhnout - totiž oni stejně jen skrývají tyto slepence... Místo 4 souborů a X řádků si jak píši vystačím s 2 metodami a to mně přijde mnohem lepší...
matesax:6.10.2012 9:04
public partial class MainForm : Form
{
private string connectionString = @"Data Source=|DataDirectory|\DataFileStorage.sdf", valuesSet = "[@id], [@name], [@lastname], [@dateofbirth], [@country], [@city], [@street], [@precinct], [@zipcode], [@mailingaddress], [@mobile], [@landline], [@email]";
public MainForm()
{
InitializeComponent();
SetDataToDatabase(new object[] { null, "Sam", "George", DateTime.Now, "Georgia", "Gradec Králové", "Bieblova", 13, 50003, "Bieblova 342, Hradec Králové 13", 732958886, 0, "[email protected]" }); //Testování funkčnosti
}
private List<List<object>> GetDataFromDatabase()
{
List<List<object>> rows = new List<List<object>>();
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT " + valuesSet.Replace("@", "") + " FROM [RegisteredUsers]";
foreach (SqlCeDataReader reader in getData.ExecuteReader())
{
List<object> row = new List<object>();
foreach (object o in reader)
row.Add(o);
rows.Add(row);
reader.Close();
}
}
return rows;
}
private void SetDataToDatabase(object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO RegisteredUsers (" + valuesSet.Replace("@", "") + ") VALUES (" + valuesSet +")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(valuesSet, ", "))
{
setData.Parameters.Add(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
setData.Parameters.Clear();
setData.CommandText = "SELECT @@IDENTITY";
setData.Dispose();
}
}
}
Kit:6.10.2012 9:38
Tak z valueSet vyhoď [@id]
.
Testování dej přímo do databáze jako integritní omezení. Nevalidní záznam nepřijme ani z jiných aplikací, odmítne nevalidní změny záznamu a dokonce i odmítne smazat záznam, pokud by tím vznikl nevalidní záznam v jiné tabulce.
matesax:6.10.2012 9:48
No to jsem také zkoušel (nebrat id):
The column name is not valid. [ Node name (if any) = ,Column name = @name ]
(Pro jistotu jsem si vyobrazil ten přidávací cyklus - k parametrům dosazuje správné hodnoty.)
Kit:6.10.2012 11:15
Jak si jinak vysvětlíš, že si stěžuje na sloupec, který se jmenuje
@name
?
matesax:6.10.2012 11:31
OK - ale co s tím? Zkusil jsem:
SqlDbType[] types = new SqlDbType[] { SqlDbType.NText, SqlDbType.NText, SqlDbType.DateTime, SqlDbType.NText, SqlDbType.NText, SqlDbType.NText, SqlDbType.Int, SqlDbType.Int, SqlDbType.NText, SqlDbType.Int, SqlDbType.Int, SqlDbType.NText };
...
SqlCeParameter parameter = new SqlCeParameter(parameterName, types[index]);
parameter.Value = parameters[index];
setData.Parameters.Add(parameter);
Výsledek stejný.
Kit:6.10.2012 11:34
Jen tak pro zajímavost: Proč se ta proměnná jmenuje
valuesSet
, když je to obyčejný string? Kde vidíš tu "množinu
hodnot"?
Kit:6.10.2012 11:39
A kde máš vlastně definovánu proměnnou name
a její
naplnění hodnotou?
matesax:6.10.2012 11:43
new object[] { "Sam", "George", DateTime.Now, "Georgia", "Gradec Králové", "Bieblova", 13, 50003, "Bieblova 342, Hradec Králové 13", 732958886, 0, "[email protected]" }
Tedy to je v parametru funkce... Zde se "Sam" ukládá jako jméno...
valueSet - beru to jako sloupce - tedy nastavování hodnot sloupců...
matesax:6.10.2012 13:02
Zkusil jsem je odstranit nic - tedy navíc to nezvládá parser - bez nich... Nechápu, jak jiným může chodit prostý výčet v kulatých závorkách - (name, lastname) - mě to jde jen s těmi hranatými - [name], [lastname] - jak píši, neměl by to být ten problém...
Byť je to prasárna - tuto možnost jsem zavrhl tímto:
...Replace("[", "").Replace("]", "")
A jakákoliv jiná úprava jekéhokoliv stringu nikam nevedla...
Kit:6.10.2012 13:13
Protože pořád pracuješ na úrovni stringů. Pokud bys té metodě předával parametry ve formě set of tuples, problémy by nenastaly.
matesax:6.10.2012 13:26
No kdybych to znal - tak bych to použil. To provní cizí co jsi zmínil, to
jsem si vygooglil a vyplynulo mi z toho, že se jedná o TableAdapter, DataSet -
atp... Tomu se chci vyhnout - proto jsem reagoval, jak jsem reagoval. Ale tohle
je pro mne již absolutní neznámá - takže kdyby jsi mi řekl o co GO,
použil bych to... Myslím
ale, že i tak by měl můj kód fungovat - respektive měl bych umět udělat
to i takto (abych si mohl zvolit ze své vůle, ne kvůli
schopnostem/znalostem).
"set" je sada nebo množina.
"tuple" se obvykle překládá jako dvojice, ale obecně je to n-tice.
C# neznám, ale představuji si to jako množinu dvojic sloupec=>hodnota. V PHP bych to předal asi takto:
SetDataToDatabase(Array("name"=>"Sam", "lastname"=>"George"));
Správnou syntaxi pro C# si někde dohledej. V PHP z toho bez problémů
vyrobím potřebný SQL dotaz. Jen bych tu metodu nazval jinak. Třeba
InsertDataToDatabase()
, protože to nedělá SET, ale INSERT.
matesax:6.10.2012 14:15
No tak n-tice znám...
Nechápu tě - přiřazuji zde:
setData.Parameters.Add(parameterName, parameters[index]);// první parametr udává kam přiřadit druhý parametr
matesax:7.10.2012 10:19
Tak se mi podařilo dát to dokupy:
using System;
using System.Windows.Forms;
using System.Data.SqlServerCe;
using System.Collections.Generic;
namespace WorkFISP
{
public partial class MainForm : Form
{
private string connectionString = @"Data Source=|DataDirectory|\DataFileStorage.sdf", valuesSet = "@name, @lastname, @dateofbirth, @country, @city, @street, @precinct, @zipcode, @mailingaddress, @mobile, @landline, @email";
public MainForm()
{
InitializeComponent();
SetDataToDatabase(new object[] { "Sam", "George", DateTime.Now, "Georgia", "Gradec Králové", "Bieblova", 13, 50003, "Bieblova 342, Hradec Králové 13", 732958886, 0, "[email protected]" });
}
private List<List<object>> GetDataFromDatabase()
{
List<List<object>> rows = new List<List<object>>();
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [RegisteredUsers]";
using (SqlCeDataReader reader = getData.ExecuteReader())
{
while (reader.Read())
{
List<object> row = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
rows.Add(row);
}
}
}
return rows;
}
private void SetDataToDatabase(object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO RegisteredUsers (" + valuesSet.Replace("@", "") + ") VALUES (" + valuesSet +")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(valuesSet, ", "))
{
setData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
}
}
}
}
Toto již nemá skoro žádný problém - tedy GetData... funguje dokonale,
ovšem SetData... nepřidá řádek. Sice žádný error - a vše funguje jak
má, ale ten řádek se nepřidá... Napadá mne, že bude problém s id sloupcem - nebo by to mohlo být
něco jiného? (Nějaké návrhy na zlepšení/zkrácení?) Děkuji.
matesax:7.10.2012 10:36
Ruším - funguje to dokonale - nějak nefunguje ShowData ve VS - normálně
tam ten řádek je - tak jedině, kdyby se ti něco nezdálo - udělal by jsi to
jinak... (Pardon za
desinformaci.)
matesax:7.10.2012 11:47
using System.Collections.Generic;
using System.Data.SqlServerCe;
class WorkWithDatabase
{
private string connectionString, columns;
public WorkWithDatabase(string connectionString, string columns)
{
this.connectionString = connectionString;
this.columns = columns;
}
public List<List<object>> GetDataFromDatabase()
{
List<List<object>> rows = new List<List<object>>();
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [RegisteredUsers]";
using (SqlCeDataReader reader = getData.ExecuteReader())
{
while (reader.Read())
{
List<object> row = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
rows.Add(row);
}
}
}
return rows;
}
public void SetDataToDatabase(object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO RegisteredUsers (" + columns.Replace("@", "") + ") VALUES (" + columns + ")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(columns, ", "))
{
setData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
}
}
public void UpdateDataInDatabase(int idOfUpdatingRow, object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand updateData = connection.CreateCommand();
updateData.CommandText = "UPDATE [RegisteredUsers] SET name=@name, lastname=@lastname, dateofbirth=@dateofbirth, country=@country, city=@city, street=@street, precinct=@precinct, zipcode=@zipcode, mailingaddress=@mailingaddress, mobile=@mobile, landline=@landline, email=@email WHERE id=@id";
updateData.Parameters.AddWithValue("@id", idOfUpdatingRow);
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(columns, ", "))
{
updateData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
updateData.ExecuteNonQuery();
}
}
public void DeleteDataFromDatabase(int idOfDeletingRow)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand removeData = connection.CreateCommand();
removeData.CommandText = "DELETE FROM RegisteredUsers WHERE id=@id";
removeData.Parameters.AddWithValue("@id", idOfDeletingRow);
removeData.ExecuteNonQuery();
}
}
}
Kit:7.10.2012 12:12
Není mi jasné, proč máš v názvech metod slovo Database
,
když pracuješ výhradně s tabulkou RegisteredUsers
. Co když
budeš chtít manipulovat s jinou tabulkou? Jak tu třídu pojmenuješ?
Třídy, objekty a metody si pojmenovávej jak chceš. Zejména pro ostatní bych však navrhl jiné názvy:
- class RegisteredUsers
- metody Insert, Update, Delete, ...
Nevidím žádný důvod všude opakovat slova "Work", "Data", "With" apod. Jen to prodlužuje zápis a znepřehledňuje program. Přehlednost programu je přitom velice důležitá. Vůbec nevadí, pokud jsou v různých třídách stejné názvy metod. Naopak je to velmi výhodné, protože to unifikuje přístup. Nemusím přemýšlet, jak se v této třídě jmenuje která metoda, protože pro vkládání budu mít vždy "Insert" a pro mazání "Delete".
Pod názvem "WorkWithDatabase" si nedokážu nic představit. Jenom to, že je to třída, která pracuje s nějakou databází. Takových tříd může být hromada a je nutné je nějak od sebe odlišit.
matesax:7.10.2012 12:52
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Data.SqlServerCe;
class TableEditor
{
private string connectionString, columns, tableName;
public TableEditor(string connectionString, string columns, string tableName)
{
this.connectionString = connectionString;
this.tableName = tableName;
this.columns = columns;
}
public List<List<object>> Select()
{
List<List<object>> rows = new List<List<object>>();
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [" + tableName +"]";
using (SqlCeDataReader reader = getData.ExecuteReader())
{
while (reader.Read())
{
List<object> row = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
rows.Add(row);
}
}
}
return rows;
}
public void Insert(object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO " + tableName + " (" + columns.Replace("@", "") + ") VALUES (" + columns + ")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(columns, ", "))
{
setData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
}
}
public void Update(int idOfUpdatingRow, object[] parameters)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand updateData = connection.CreateCommand();
string build = "";
foreach (string s in Regex.Split(columns, ", "))
build += s.Replace("@", "") + "=" + s + ", ";
updateData.CommandText = "UPDATE [" + tableName + "] SET " + build.Substring(0, build.Length - 2) + " WHERE id=@id";
updateData.Parameters.AddWithValue("@id", idOfUpdatingRow);
int index = 0;
foreach (string parameterName in Regex.Split(columns, ", "))
{
updateData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
updateData.ExecuteNonQuery();
}
}
public void Delete(int idOfDeletingRow)
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
connection.Open();
SqlCeCommand removeData = connection.CreateCommand();
removeData.CommandText = "DELETE FROM " + tableName + " WHERE id=@id";
removeData.Parameters.AddWithValue("@id", idOfDeletingRow);
removeData.ExecuteNonQuery();
}
}
}
Kit:7.10.2012 13:09
Hmm, tohle vypadá mnohem lépe. Navíc jsi zobecnil název tabulky, to je ještě lepší. Teď už jen pohlídat, aby byly '[' a ']' kolem každého tableName.
Přidal bych ještě metodu 'Search', která vybere jen ty záznamy, které
hledám. Ovšem to by se dalo ještě dál rozšiřovat i na další metody,
protože když updatuji nebo mažu záznamy, tak vždy neznám jejich
id
a často to ani není žádoucí.
Ještě mi není jasné, proč máš v každé metodě volání
connection.Open()
. Doufám, že to otvírá databázi jen napoprvé
a otevřené spojení si udržuje.
matesax:7.10.2012 13:18
Id zjišťuji jinde.
Nepřišlo mi příhodné mít celou dobu otevřené spojení - proto je to v using - stačí jen otevřít a using se o uzavření postará...
matesax:7.10.2012 13:23
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Data.SqlServerCe;
class TableEditor
{
private string connectionString, columns, tableName;
private SqlCeConnection connection;
public TableEditor(string connectionString, string columns, string tableName)
{
this.connectionString = connectionString;
this.tableName = tableName;
this.columns = columns;
connection = new SqlCeConnection(connectionString);
connection.Open();
}
public List<List<object>> Select()
{
List<List<object>> rows = new List<List<object>>();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [" + tableName + "]";
using (SqlCeDataReader reader = getData.ExecuteReader())
{
while (reader.Read())
{
List<object> row = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
rows.Add(row);
}
}
return rows;
}
public void Insert(object[] parameters)
{
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO [" + tableName + "] (" + columns.Replace("@", "") + ") VALUES (" + columns + ")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(columns, ", "))
{
setData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
}
public void Update(int idOfUpdatingRow, object[] parameters)
{
SqlCeCommand updateData = connection.CreateCommand();
string build = "";
foreach (string s in Regex.Split(columns, ", "))
build += s.Replace("@", "") + "=" + s + ", ";
updateData.CommandText = "UPDATE [" + tableName + "] SET " + build.Substring(0, build.Length - 2) + " WHERE id=@id";
updateData.Parameters.AddWithValue("@id", idOfUpdatingRow);
int index = 0;
foreach (string parameterName in Regex.Split(columns, ", "))
{
updateData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
updateData.ExecuteNonQuery();
}
public void Delete(int idOfDeletingRow)
{
SqlCeCommand removeData = connection.CreateCommand();
removeData.CommandText = "DELETE FROM [" + tableName + "] WHERE id=@id";
removeData.Parameters.AddWithValue("@id", idOfDeletingRow);
removeData.ExecuteNonQuery();
}
}
Kit:7.10.2012 13:36
To právě není žádoucí Id
zjišťovat jinde a ještě
mezitím zavírat spojení s databází. Aplikace se tím velmi zpomalí. Je
mnohem lepší si udržovat otevřené spojení po celou dobu běhu
aplikace.
Pokud zjišťuješ Id
jinde, tak velmi snadno může dojít k
tzv. Race Condition neboli souběhu a tím k nekonzistenci dat. Ne že by se
databáze fyzicky poškodila, ale záznam může být třeba 2× modifikován, i
když by měl být modifikován pouze jednou.
Tohle je jeden z důvodů, proč nemám rád ORM frameworky. Ty jednodušší to neumí a ty složitější jsou zase pomalé.
Ještě mi tam chybí metoda pro vyhledání jednoho záznamu podle nějaké podmínky.
matesax:8.10.2012 8:42
OK - nyní mi stačí jen ID řádku - toto ID je dostupné. (Dostanu jej při načtení tabulky.) Dále mi již stačí jen znát connection string a název tabulky - již nepotřebuji znát sloupce:
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Data;
public class TableEditor
{
private string connectionString, columns, tableName;
private SqlCeConnection connection;
public TableEditor(string connectionString, string tableName)
{
this.connectionString = connectionString;
this.tableName = tableName;
connection = new SqlCeConnection(connectionString);
connection.Open();
string build = "";
SqlCeCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM [" + tableName + "]";
using (SqlCeDataReader reader = command.ExecuteReader())
for (int index = 1; index < reader.FieldCount; index++)
build += "@" + reader.GetName(index) + ", ";
columns = build.Substring(0, build.Length - 2);
}
public List<List<object>> Select()
{
List<List<object>> rows = new List<List<object>>();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [" + tableName + "]";
using (SqlCeDataReader reader = getData.ExecuteReader())
while (reader.Read())
{
List<object> row = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
rows.Add(row);
}
return rows;
}
public List<object> SelectRow(int selectedId)
{
List<object> row = new List<object>();
SqlCeCommand getData = connection.CreateCommand();
getData.CommandText = "SELECT * FROM [" + tableName + "]";
using (SqlCeDataReader reader = getData.ExecuteReader())
while (reader.Read())
if ((int)reader.GetValue(0) == selectedId)
for (int i = 0; i < reader.FieldCount; i++)
row.Add(reader.GetValue(i));
return row;
}
public void Insert(object[] parameters)
{
SqlCeCommand setData = connection.CreateCommand();
setData.CommandText = "INSERT INTO [" + tableName + "] (" + columns.Replace("@", "") + ") VALUES (" + columns + ")";
int index = 0;
foreach (string parameterName in System.Text.RegularExpressions.Regex.Split(columns, ", "))
{
setData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
setData.ExecuteNonQuery();
}
public void Update(int idOfUpdatingRow, object[] parameters)
{
SqlCeCommand updateData = connection.CreateCommand();
string build = "";
foreach (string s in Regex.Split(columns, ", "))
build += s.Replace("@", "") + "=" + s + ", ";
updateData.CommandText = "UPDATE [" + tableName + "] SET " + build.Substring(0, build.Length - 2) + " WHERE id=@id";
updateData.Parameters.AddWithValue("@id", idOfUpdatingRow);
int index = 0;
foreach (string parameterName in Regex.Split(columns, ", "))
{
updateData.Parameters.AddWithValue(parameterName, parameters[index]);
index++;
}
updateData.ExecuteNonQuery();
}
public void Delete(int idOfDeletingRow)
{
SqlCeCommand removeData = connection.CreateCommand();
removeData.CommandText = "DELETE FROM [" + tableName + "] WHERE id=@id";
removeData.Parameters.AddWithValue("@id", idOfDeletingRow);
removeData.ExecuteNonQuery();
}
}
Kit:8.10.2012 9:53
Proč vlastně načítáš celou tabulku? To musí být děsně líné. Také při spolupráci více programů to může způsobit problémy. Načti si jen záznamy, které skutečně potřebuješ.
matesax:8.10.2012 10:03
Což je celá tabulka... Mám možnost zvolit si i jen jeden řádek. A více možností nepotřebuji...
Zobrazeno 31 zpráv z 31.