Managing data access in a simple WinForms app - c#

I have a simple WinForms data entry app that uses SQLite. It will always be a single-user app and always with a local database. I have multiple tabs, with UserControls serving as content for the tabs. Each time a tab is selected, an appropriate UserControl is initialized, and the old one is removed (using TabPage.Controls.Remove).
Each UserControl initializes a generic DataAccess object, which wraps all the database stuff and can be reused with any tab content. The issue is that I have an open SQLiteConnection for the duration of the life of the tab (UserControl). I've read elsewhere that it's not a good practice. I don't want to overkill on the design with elaborate data layers and business object layers, partly because I don't know how to do it, and partly because I don't think it's necessary for this app.
I'm basically keeping the same connection, adapter, DataTable, SqlCommand, etc objects in memory and just reusing them with different sql query parameters, and to get that cached data with other methods (like RowCount). I had a problem with LoadData method as it was not clearing out previous query results from DataTable, so I'm doing it manually in the beginning.
I tried figuring out a way to use "using" with SQLiteConnection and other objects, but then I'd have to redo the whole DataLoad stuff or similar for simple things like RowCount. So I'm just looking for suggestions and comments on this approach with data access.
Below is my DataAccess class.
public class DataAccess
{
private SQLiteConnection connection = new SQLiteConnection(Global.DbConnectionString);
private DataTable dataTable = new DataTable();
private SQLiteDataAdapter dataAdapter = new SQLiteDataAdapter();
private SQLiteCommandBuilder commandBuilder = new SQLiteCommandBuilder();
private SQLiteCommand command = new SQLiteCommand();
private BindingSource bindingSource = new BindingSource();
public DataAccess()
{
dataAdapter.SelectCommand = command;
commandBuilder.DataAdapter = dataAdapter;
bindingSource.DataSource = dataTable;
}
~DataAccess()
{
connection.Dispose();
}
public BindingSource BindingSource
{
get { return bindingSource; }
}
///*
public void LoadData(string sql, Dictionary<string, string> parameters)
{
try
{
dataTable.Clear();
command.Connection = connection;
// Ignore sql parameter if we already have CommandText. This assumes sql never changes per instance
if (command.CommandText == null)
command.CommandText = sql;
foreach (KeyValuePair<string, string> parameter in parameters)
{
if (command.Parameters.Contains(parameter.Key))
command.Parameters[parameter.Key].Value = parameter.Value;
else
{
command.Parameters.Add(new SQLiteParameter(parameter.Key));
command.Parameters[parameter.Key].Value = parameter.Value;
}
}
dataAdapter.Fill(dataTable);
}
catch (SqlException)
{
MessageBox.Show("Data Problem, need to display what's wrong later");
}
}//*/
public int RowCount()
{
return dataTable.Rows.Count;
}
public string GetFieldValue(int row_index, string column_name)
{
return dataTable.Rows[row_index][column_name].ToString();
}
public void Save()
{
dataAdapter.Update(dataTable);
}
public void NewRow(Dictionary<string, string> fields)
{
DataRow dataRow = dataTable.NewRow();
foreach (KeyValuePair<string, string> field in fields)
dataRow[field.Key] = field.Value;
dataTable.Rows.Add(dataRow);
}
}

If you want to do it nicely, you should create a data access layer that would expose methods to fetch the data and modify it. This layer would open a connection whenever it's necessary and then close it. You could add a caching layer on top of it. And your GUI would only use the data objects from the lower layers.
It's not a small rewrite, so if your current solution works, and you don't want to put much effort into it, then just leave it like this, it's not that bad. If it's a simple program, then this simple solution is just fine.

Related

How To Search Item In user control Panel?

When I try to search data in my user control from my database it does search or filter the data that I typed in the search textbox. Here's the code that I'm using to try and search or filter
SqlConnection cn;
SqlCommand cm;
SqlDataReader dr;
private Label name;
private Label amount;
private Label descrip;
public Form1()
{
InitializeComponent();
cn = new SqlConnection(#"Data Source=(LocalDB)");
}
private void Form1_Load(object sender, EventArgs e)
{
GetData();
}
private void GetData()
{
cn.Open();
cm = new SqlCommand("Select * from Bills where (billname) like '%" + txtSearch.Text + "%'", cn);
dr = cm.ExecuteReader();
while (dr.Read())
{
long len = dr.GetBytes(0, 0, null, 0, 0);
byte[] array = new byte[System.Convert.ToInt32(len) + 1];
dr.GetBytes(0, 0, array, 0, System.Convert.ToInt32(len));
name = new Label();
name.Text = dr["billname"].ToString();
descrip = new Label();
descrip.Text = dr["billdescrip"].ToString();
amount = new Label();
amount.Text = dr["billamount"].ToString();
}
dr.Close();
cn.Close();
}
private void txtSearch_TextChanged(object sender, EventArgs e)
{
GetData();
}
When I type something into the txtSearch.text box, the results come back empty and doesn't display the what im trying to search for in the txtSearch.text box.
"Select * from Bills where (billname) like '%" + txtSearch.Text + "%'"
It seems to me that you have a database table Bills, with a column BillName. The operator types some text in TextBox txtSearch, and you want to fetch all Bills that have a BillName that starts with the text in the TextBox.
I see several problems here
SQL Injection
SQL injection is a code injection technique that might destroy your database.
SQL injection is one of the most common web hacking techniques.
SQL injection is the placement of malicious code in SQL statements
Look what your Sql text would be if the operator types the following text:
"John%; DROP TABLE Bills;--"
Select * from Bills where (billname) like %John%; DROP TABLE Bills; --%
You would lose all Bills!
More information about SQL Injection
Solution: Never, ever add input data into your sql string! Always add it as a parameter!
Start using using statements
The database connection is a scarce resource: you should not keep it alive longer than needed. Also, if you SQL query encounters an exception, the connection and the datareader are not closed.
Make it a habit, that whenever an object implements IDisposable you should use it using a using statement.
This way, you can be assured that whatever happens, at the end of the using statement everything is properly flushed, written, closed and disposed.
SqlConnection, SqlCommand and SqlDataReader should be private members of GetData. This way you can be certain that no one can tamper with your connection; you hide how you fetch the data from the database (SQL and SqLCommand, or Entity Framework and LINQ?), thus making future changes easier. Readers of your code won't have to check where these variables are used, and that no one is misusing it, thus making your code easier to understand. And of course this will make it possible to reuse GetData for other purposes.
Which brings me to the third improvement:
Separate data from how it is displayed
In modern programming you see more and more a separation between the date (= model) and the way that the data is displayed (= view).
Separation makes it better to reuse the code, for instance: if you want to use your model in a console program, or in a WPF program, or even a different Form, you can reuse the model classes.
Separation hides how and where you fetch your data: is it a database? is it a CSV file, or XML? are you using Entity Framework
This hiding allows future changing without having to change all your Forms
This hiding also makes your Forms smaller and easier to understand
While developing the form, you can mock the actual data: just create a dummy class that provides you with sample data, without having to bother about the database
You can unit test the model, without needing a form
It is hardly any extra work.
So you'll have Model classes: your data, and how it is saved, fetched again; and View classes: your Forms. You'll need an adapter class to adapt the model to the view: the ViewModel. Together these three abbreviate to MVVM. Consider to do some background reading about MVVM.
Implementing the three advices
We create a class that makes it possible to save Bills (and other items: Customers? Orders? Products? etc) and later you can retrieve them again, even after you restarted the program. Something like a warehouse, a repository, where you store items and fetch them again.
interface IOrderRepository
{
int AddBill(Bill bill); // return Id of the Bill
Bill FindBill(int Id); // null if not found
// your GetData:
IEnumerable<Bill> FetchBillsWithNameLike(string name);
... // other methods, about Customers, Orders, etc
}
Implementation:
class OrderRepository : IOrderRepository
{
private string ConnectionString {get;} = #"Data Source=(LocalDB)";
private IDbConnection CreateConnection()
{
return new SqlConnection(this.ConnectionString);
}
The implementation of FetchBillsWithNameLike:
public IEnumerable<Bill> FetchBillsWithNameLike(string name)
{
using (IDbConnection dbConnection = this.CreateConnection())
{
const string sqlText = "Select Id, BillName, CustomerId, ..."
+ " from Bills where (billname) like %#Name%";
using (IDbCommand = dbConnection.CreateCommand())
{
// fill the command and the parameter:
dbCommand.CommandText = sqlText;
dbCommand.AddParameterWithValue("#Name", name);
// execute the command and enumerate the result
dbConnection.Open();
using (IDatareader dbReader = dbCommand.ExecuteReader())
{
while (dbReader.Read())
{
// There is a Bill to read
Bill bill = new Bill
{
Id = dbReader.ReadInt32(0),
Name = dbReader.ReadString(1),
CustomerId = dbReader.ReadInt32(2),
...
};
yield return bill;
}
}
}
}
}
// implement rest of interface
}
Several improvements:
Connection string is a property. If you decide to use a different connection string for all your 100 methods: only one place to change.
You hide where you get the connection string: here it is a constant, but if you decide in future versions to read it from the config file: no one has to know, except this method
You hide that you are using a SqlConnection, you return the interface. If in future versions you decide to create a different form of IDbConnection, for instance for a different kind of database, like SQLite, no one has to know that you create a SqlLiteConnection object instead of a SqlConnection.
Similarly: hide SqlCommand, use the interface IDbCommand.
The database connection is not opened before it is needed. This makes it possible that others can use the database as long as you will not use it.
using statements all over the place: if any exception happens, all objects are properly closed and disposed.
I don't create the DbCommand myself, I ask the DbConnection to create it for me, so I don't have to bother which type of commands the actual DbConnection uses: Is it SqlCommand? SQLiteCommand?
I specify which columns from the table if want. If in future some columns are added, and I don't need them, I won't fetch more data than I want. Similarly: if columns are reordered, it will still work.
The most important change: Use SQL parameter to prevent malicious SQL Injection.
Parameters in SQL text are often recognized by prefix #
Parameters are added with the extension method AddParameterWithValue`. Some databases have this as a method in DbCommand (for example: SQLite)
When reading the fetched data, I won't read more Bills than my caller wants. So if he calls me using the following code: not all Bills will be read:
IOrderRepository repository = ...
string name = this.ReadName();
bool billsWithNameAvailable = repository.FetchBillsWithName(name).Any();
Here, my caller only wants to know if there are any Bills with the Name at all. The reader won't create any Bills at all.
Because the SQL text is Select Id, ... and I read dbReader.GetInt32[0] etc, my code will still work, even after inserting or reordering the columns of the table.
The nice thing is, that you will be able to unit test method FetchBillsWithName without having to use a Form: you can test what happens if there is no database at all, or if there is no Bills table, or an empty table, or the table doesn't contain a column BillName. Or what happens if the input text is empty. You can unit test all kinds of errors without need of a Form.
The Form
class Form1 : ...
{
private IOrderRepository Repository {get;} = new OrderRepository();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
GetData();
}
private void GetData()
{
string name = this.txtSearch.Txt;
foreach(Bill fetchedBill in this.Repository.FetchBillsWithNameLike(name))
{
this.ProcessBill(fetchedBill);
}
}
private void ProcessBill(Bill fetchedBill)
{
// do your stuff with the label;
}
}
Because I separated the model from the view, the view is much simpler: much easier to see what really happens: you focus on the form, not on how and where you get the data.
During development, while you don't have a database yet, you can create a dummy repository and test your form:
class DummyRepository : IOrderRepository
{
private Dictionary<int, Bill> Bills {get;} = ... // fill with some sample Bills
// TODO: implement IOrderRepository, using this.Bills
}
If later you decide that you won't get your data from a database, but for instance from the internet, your form will hardly have to change. It can still use IOrderRepository
Conclusion
By separating the model from the view, both model and view are much easier to red and understand. Much easier to reuse, change, maintain and unit test. Both can be developed independantly
Procedure are small and have only one task: this makes that we can reuse the procedures. Changes are only in one procedure
By using interfaces, I hide how and where the data is fetched: SQL? CSV-file? Internet?
By using using statements the program is more full proof: after exceptions everything is property closed and disposed
By using SQL parameters I prevented malicious use of SQL injection.

c# how update datagridview changes when data adpreter is in antoher project

I have 3 projects in solution
Data
Bussnies
Presentation (win forms)
In presentation i have DataGridView where i show data from Data layer. In data layer i have repository classes for returning data from database.
My question is next:
To save all changes i need access to Adapter but my adapter is stored in Data layer. In presentation i get Bussniess instance and get database records from bussnes layer.
Method from data:
public DataSet GetAll()
{
DataSet ds = new DataSet();
using (var conn = new MySqlConnection(DB.ConnectionString))
{
try
{
conn.Open();
using (MySqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "Subjekti_Tip_List";
cmd.CommandType = CommandType.StoredProcedure;
using (MySqlDataAdapter adapter = new MySqlDataAdapter(cmd))
{
adapter.Fill(ds);
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
return ds;
}
Bussnies:
public DataSet GetAll()
{
return reposotory.GetAll();
}
Presentation
void Table_RowChanged
(object sender, DataRowChangeEventArgs e)
{
if (e.Row.RowState == DataRowState.Modified)
{
TableAdapter.Update(e.Row); // HERE I NEED INSTANCE OF ADAPTER FROM DATA LAYER
}
}
So here is problem i want update changes and save it to database. In Presentation layer i dont have access to adapter how can i do this to be good and maintnanced.
The first two code snippets are textbook implementation of the 3-tier architecture design. But, you broke that in the last code snippet. The front-end/presentation/UI layer (name it) should never takeover the DAL's role and deal directly with the back-end/DB. As I commented, you need to preserve the design and imitate the first two code snippets for the Update/Delete/Insert/Count/Sum ...etc. routines. For example, let's highlight the Update part.
DAL
Add Update method to be called by the BLL. You can add more overloads to pass different data objects and call the proper Update overload. Please note, the DAL is the only place in this design for data objects like connections, commands, adapters, transactions, command builders.
public void Update(DataTable dt)
{
// The update routine. Here is where you should use the data adapter...
}
public void Update(DataSet ds)
{
// ...
}
public void Update(DataRow[] rows)
{
// ...
}
BLL - The Agent
Add BLL methods, called by the presentation layer. You can add routines here to validate the inputs, throw exception for null/invalid arguments ...etc.
public void Update(DataTable dt)
{
// Validate, throw or proceed...
reposotory.Update(dt);
}
public void Update(DataSet ds)
{
reposotory.Update(ds);
}
public void Update(DataRow[] rows)
{
reposotory.Update(rows);
}
UIL
In your front-end, I presume you have a reference to the BLL project and instance of it. Let's name it MyBLL.
void Caller()
{
// Update a DataSet...
MyBLL.Update(yourDataSetIfAny);
yourDataSetIfAny.AcceptChanges(); // Only when you get a successful update.
// OR
DataTable dt = // Get the DataTable..
MyBLL.Update(dt);
dt.AcceptChanges(); // On a successful update.
// OR
MyBLL.Update(dt.GetChanges()); // See the GetChanges overloads..
dt.AcceptChanges();
// OR
MyBLL.Update(dt.AsEnumerable()
.Where(x => x.RowState == DataRowState.Modified).ToArray());
// DO NOT call dt.AcceptChanges(); here if the dt still contains deleted or new rows.
}

Create interface for custom Adapter and DataTable

I am learning to use interfaces and there is a problem when using the generated dataset.
I created two similar tables in the database, then created two data adapters in the dataset based on these tables. Then I created classes that will describe the adapter, datable and necessary methods.
My DataSet
class DB
{
public class Table1 {
public DataSetTableAdapters.Table1TableAdapter adapter;
public DataSet.Table1DataTable dataTable;
public void Init()
{
adapter = new DataSetTableAdapters.Table1TableAdapter();
dataTable = new DataSet.Table1DataTable();
}
}
public class Table2
{
public DataSetTableAdapters.Table2TableAdapter adapter;
public DataSet.Table2DataTable dataTable;
public void Init()
{
adapter = new DataSetTableAdapters.Table2TableAdapter();
dataTable = new DataSet.Table2DataTable();
}
}
}
I tried to implement, for example, like this
interface ISecondaryTable
{
DataTable dataTable { get; set; }
IDataAdapter adapter { get; set; }
}
And I tried many other options. But without results.
Does anyone know their common class?
If you open the Object Browser in visual studio (Ctrl+Alt+J in 2017, used to be Ctrl-W,J as a chord) you can see more info about the tableadapters in your project:
All TableAdapters inherit from Component. They "HAVE-A" DataAdapter, they are not "IS-A" DataAdapter. For example, they look like this:
public class XTableAdapter: Component{
private DataAdapter _da;
}
They do not look like this:
public class XTableAdapter: DataAdapter
All this said, I'm not sure why you want to treat them this way or encapsualte them along with the data table. Data is stored in the datatable, tableadapters push it between db and datatable. I don't think i've ever seen someone do, for example, a class that wraps a StreamWriter (thing that writes a file) and a String (the content of the file):
class CombinedFileContentAndWriter{
StreamWriter sw = newStreamWriter(#"C:\temp\x.txt");
string content = "Hello World";
void DoIt(){
sw.Write(content);
}
}
It's not to say you can't, it's just weird. TableAdapters are supposed to be short-to-medium life things that are called upon to move data; they don't need pairing up inseparably from that data. One tableadapter can readwrite hundreds of different instances of a datatable. TableAdapters can be created and thrown away on demand and they don't need to remain paired with the data they downloaded in order to function. You can:
var dt = new XTableAdapter().GetDataByName();
//manipulate dt in a 30 minute operation
new XTableAdapter().Update(dt); //a different tableadapter sends the data back to the DB
There's not much point trying to find a generic way to refer to tableadapters, becawuse they are all customized exactly to a specific datatable, and have names of methods that are variable, and hence not overloadable:
SchoolDataset ds = new SchoolDataSet();
new StudentTableAdapter.FillByStudentId(ds, 123);
new StaffTableAdapter.FillByStaffLastName(ds, "Smith");
new ClassroomTableAdapter.FillByYearDesignation(ds, "First grade");
This is about as deep as you need to go with tableadapters and typed datatables/sets.
The question Karen linked to is a smart resource, for sure, but it's worth noting that the lead answer chose to create a generic way to address tableadapters so they could easily enroll each adapter in a transaction, whereas Microsoft intended that to enroll tableadapter operations in a transaction they should be executed inside a TransactionScope

C#.NET sqlDatabase CommandBuilder and DataAdapter: The name 'Adapter' does not exit in the current context

I've been following a C# .Net tutorial online. Created a class and have declare the variable Adapter1 for my DataAdapter.
I'm using CommandBuilder with Adapter1 to update, save, delete or insert new record to the database.
The problem I'm having is that the UpdateDatabase method I've declared seems not to see the variable Adapter1.
Please take a look at the code below and tell me what I'm doing wrong. Code that's causing error is at the very bottom
private string sql_string;
private string strCon; //This is a write-only property
public string Sql
{
set { sql_string = value; }
}
public string connection_string
{
set { strCon = value; }
}
public System.Data.DataSet GetConnection
{
get { return MyDataSet(); }
}
private System.Data.DataSet MyDataSet()
{
System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(strCon);
con.Open();
System.Data.SqlClient.SqlDataAdapter Adapter1 = new System.Data.SqlClient.SqlDataAdapter(sql_string, con);
System.Data.DataSet dat_set = new System.Data.DataSet();
Adapter1.Fill(dat_set, "Table_Data_1");
con.Close();
return dat_set;
}
public void UpdateDatabase (System.Data.DataSet ds)
{
System.Data.SqlClient.SqlCommandBuilder cb = new System.Data.SqlClient.SqlCommandBuilder(Adapter1);
cb.DataAdapter.Update(ds.Tables[0]);
}
The reason you're getting this specific error is that there is no variable named Adapter1 that is in scope in function UpdateDatabase(). You could make the local variable that you've declared in function MyDataSet() a member variable, then it would be visible in UpdateDatabase(). This is not the best approach, however, because you'll be counting on code that uses this helper class to always call the MyDataSet() function before the UpdateDatabase() function, otherwise the data adapter won't be initialized (you'll get a null reference error). If you want to structure the code this way, you should initialize your adapter in the constructor of the class that you're building, that way the code that uses your library can be sure that the adapter will always be initialized properly. If you structure the code that way, you can make the connection string a constructor parameter instead of a write- only property.
By all means keep working on this code as an exercise, but be aware that DataAdapters and DataSets are a very old part of .NET, and this idiom isn't used much in modern data tiers. Either people use ORMs (like EntityFramework) or they use ADO.NET commands directly if they want control and efficiency. I would also point out that the class you're building appears to be trying to fill the role of a DataAdapter, so it wouldn't have any utility in real life (people would just use a SqlDataAdapter directly instead of your class).

Use of SqlDataSource From Non-Control Situations

As part of my common utilities I used in all my line of business applications, I have this code...
using System.Web.UI.WebControls;
public class Database
{
/// <summary>
/// Creates a DataView object using the provided query and an SqlDataSource object.
/// </summary>
/// <param name="query">The select command to perform.</param>
/// <returns>A DataView with data results from executing the query.</returns>
public static DataView GetDataView(string query)
{
SqlDataSource ds = GetDBConnection();
ds.SelectCommand = query;
DataView dv = (DataView)ds.Select(DataSourceSelectArguments.Empty);
return dv;
}
/// <summary>
/// Creates a SqlDataSource object with initialized connection string and provider
/// </summary>
/// <returns>An SqlDataSource that has been initialized.</returns>
public static SqlDataSource GetDBConnection()
{
SqlDataSource db = new SqlDataSource();
db.ConnectionString = GetDefaultConnectionString(); //retrieves connection string from .config file
db.ProviderName = GetDefaultProviderName(); //retrieves provider name from .config file
return db;
}
}
Then, in my projects, to retrieve data from databases I'll have some code like..
DataView dv=Database.GetDataView("select mycolumn from my table");
//loop through data and make use of it
I have taken some heat from people for using SqlDataSource in this manner. People don't seem to like that I'm using a Web control purely from code instead of putting it on an ASPX page. It doesn't look right to them, but they haven't been able to tell me a downside. So, is there a downside? This is my main question. Because if there's a lot of downsides, I might have to change how I'm doing many internal applications I've developed.
My Database class even works from non-ASP.NET situations, so long as I add the System.Web assembly. I know it's a slight increase in package size, but I feel like it's worth it for the type of application I'm writing. Is there a downside to using SqlDataSource from say a WPF/Windows Forms/Console program?
Well, there are no hard rules stopping anyone from doing such implementation.
However, following are few questions that need to be answered before doing that implementation.
Is this usage thread safe? (because there is every possibility the same call can be made by multiple consuming applications.
Will there be a layered differentiation (UI.Control being used in a Data layer)?
What if that control becomes obsolete / restricted in the next framework releases?
Given how easy it is to replace this code, whilst removing the temptation to use dynamic SQL queries to pass parameters, I think the question should be: is there any benefit to keeping the code as-is?
For example:
public static class Database
{
private static readonly Func<DbCommandBuilder, int, string> getParameterName = CreateDelegate("GetParameterName");
private static readonly Func<DbCommandBuilder, int, string> getParameterPlaceholder = CreateDelegate("GetParameterPlaceholder");
private static Func<DbCommandBuilder, int, string> CreateDelegate(string methodName)
{
MethodInfo method = typeof(DbCommandBuilder).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, new Type[] { typeof(Int32) }, null);
return (Func<DbCommandBuilder, int, string>)Delegate.CreateDelegate(typeof(Func<DbCommandBuilder, int, string>), method);
}
private static string GetDefaultProviderName()
{
...
}
private static string GetDefaultConnectionString()
{
...
}
public static DbProviderFactory GetProviderFactory()
{
string providerName = GetDefaultProviderName();
return DbProviderFactories.GetFactory(providerName);
}
private static DbConnection GetDBConnection(DbProviderFactory factory)
{
DbConnection connection = factory.CreateConnection();
connection.ConnectionString = GetDefaultConnectionString();
return connection;
}
public static DbConnection GetDBConnection()
{
DbProviderFactory factory = GetProviderFactory();
return GetDBConnection(factory);
}
private static void ProcessParameters(
DbProviderFactory factory,
DbCommand command,
string query,
object[] queryParameters)
{
if (queryParameters == null && queryParameters.Length == 0)
{
command.CommandText = query;
}
else
{
IFormatProvider formatProvider = CultureInfo.InvariantCulture;
DbCommandBuilder commandBuilder = factory.CreateCommandBuilder();
StringBuilder queryText = new StringBuilder(query);
for (int index = 0; index < queryParameters.Length; index++)
{
string name = getParameterName(commandBuilder, index);
string placeholder = getParameterPlaceholder(commandBuilder, index);
string i = index.ToString("D", formatProvider);
command.Parameters.AddWithValue(name, queryParameters[index]);
queryText = queryText.Replace("{" + i + "}", placeholder);
}
command.CommandText = queryText.ToString();
}
}
public static DataView GetDataView(string query, params object[] queryParameters)
{
DbProviderFactory factory = GetProviderFactory();
using (DbConnection connection = GetDBConnection(factory))
using (DbCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
ProcessParameters(factory, command, query, queryParameters);
DbDataAdapter adapter = factory.CreateDataAdapter();
adapter.SelectCommand = command;
DataTable table = new DataTable();
adapter.Fill(table);
return table.DefaultView;
}
}
}
With this version, you can now pass in parameters simply and safely, without relying on custom code to try to block SQL injection:
DataView dv = Database.GetDataView(
"select mycolumn from my table where id = {0} and name = {1}",
1234, "Robert');DROP TABLE Students;--");
EDIT
Updated to support parameters for different providers, with help from this answer.
The only issues I see are
(1) this is like reinventing the wheel. There is Enterprise library v5 for FW3.5 and v6 for FW4.5, which has data access components. Use that.
With EL you can make a call and have 2,3,4 tables loaded in Dataset. With your method this is not possible, only one at the time.
Enterprise library is a complete Data Access suite provided by Microsoft. It takes care of all the little details and all you need is to call your data. This is complete data access layer. And if you look deeper, EL allows for integration of Data and Caching, and other things. But you don't have to use what you don't need. If you need data access you can use only that.
And (2) Generally, this is not a good idea to write low level assembly with high-level assembly in reference. Anything System.Web.... is UI and client related stuff. In a layered cake design this is like the top of it and Data Access is on the bottom. All references [save for "common"] should travel from bottom to the top and you have it in opposite direction.
Look at this picture:
This is from Microsoft. You see the layers of the "cake". All references are going up. What you've done - you took UI-related component and wrote Data Access in it.
You can call it opinion-based - but this opinion is standard practice and pattern in software development. Your question is also opinion based. Because you can code everything in single file, single class, and it will work. You can set references to System.Windows.Forms in Asp.net application, if you want to. Technically, it is possible but it is really bad practice.
Your application now have limited reusability. What if you write WPF component or service that need to use same Data Access. You have to drag all System.Web into it?

Categories