How to execute RAW SQL Query with EF Core? - c#

Basically the problem i have is that i want to run a query in a database that it's not a representation of my model.
This is my code to create the connection to another database:
public static OtherContext GetNewContextGeneric(string connectionString)
{
var builder = new DbContextOptionsBuilder();
builder.UseSqlServer(connectionString);
OtherContext db = new OtherContext(builder.Options);
return db;
}
And this is my code to execute the query:
public List<IQueryble> Query (string connectionString, string query)
{
try
{
using(var contextGeneric = ContextFactory.GetNewContextGeneric(connectionString))
{
//I want something like this
return contextGeneric.Query(query).ToList();
}
}
catch(System.Data.SqlClient.SqlException ex)
{
throw new SQLIncorrectException(ex);
}
catch(System.InvalidOperationException ex)
{
throw new NotImplementedException();
}
}
Can somebody help me?

You can use DbDataReader:
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT * From Make";
context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
// Do something with result
reader.Read(); // Read first row
var firstColumnObject = reader.GetValue(0);
var secondColumnObject = reader.GetValue(1);
reader.Read(); // Read second row
firstColumnObject = reader.GetValue(0);
secondColumnObject = reader.GetValue(1);
}
}
Here you can learn more how to read values from DbDataReader.
Alternatively you could use FromSql() method, but that works only on predefined DbSet of some entity, which is not the solution you wanted.

In the question you say:
Basically the problem i have is that i want to run a query in a database that it's not a representation of my model.
and then in comments you add:
Because i don't know how is created the database, i don't know what tables are in the database i want to insert the sql query
Well, if you don't know the database, then you cannot use Entity Framework, as it requires you to have a detailed knowledge of the database you are connecting to.
You should use plain ADO.NET (or Dapper if you want to map results back to a known class) for this.

Your use case of an external database can still be achieved using EF Core, there is no need to resort to ADO.Net, this solution is based on this generic solution for EF Core by #ErikEj. (Note that some functions and namespaces changed for EF Core 3, so and remain in .Net 5+ but the same generic concept can still be applied)
public static IList<T> Query<T>(string connectionString, string query, params object[] parameters) where T : class
{
try
{
using (var contextGeneric = new ContextForQuery<T>(connectionString))
{
return contextGeneric.Query<T>().FromSql(query, parameters).ToList();
}
}
catch (System.Data.SqlClient.SqlException ex)
{
throw new SQLIncorrectException(ex);
}
catch (System.InvalidOperationException ex)
{
throw new NotImplementedException();
}
}
private class ContextForQuery<T> : DbContext where T : class
{
private readonly string connectionString;
public ContextForQuery(string connectionString)
{
this.connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString, options => options.EnableRetryOnFailure());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<T>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
}
Then the usage of this requires a concrete type definition, to add support for anonymous types is a fair bit more effort, but creating a concrete type for this is not a bad thing, the whole point here is to try you towards more declarative code styles as they enhance the readability and inspection of the code as well as providing documentation and other extended configuration like related entities.
public class NamedObject
{
public int Id { get; set; }
public string Name { get; set; }
}
...
var connectionString = "Insert your connection string here...";
var data = Query<NamedObject>(connectionString, "SELECT TOP 10 Id, FullName as Name FROM Employee");
foreach (var emp in data)
{
Console.WriteLine(emp.Name);
}
Background
In EF 6 (.Net Framework) we could use DbContext.Database.FromSQL<T>() to execute ad-hoc SQL that would be automatically mapped to the specified type of T. This functionality was not replicated in EF Core because the result of FromSQL was inconsistent with the rest of EF, the result was a single use IEnumerable<T>. You could not further compose this query to Include() related entities nor could you add a filter to the underlying query.
In EF Core to Execute Raw SQL the type T that you want to return needs to be defined in the DbContext as a DbSet<T>. This set does not need to map to a table in the database at all, in fact since EF Core 2.1 we do not event need to specify a key for this type, it is simply a mechanism to pre-define the expected structure instead of executing Ad-Hoc requests on demand, it offers you the same functionality as the legacy FromSQL but also allows for you to define a rich set of navigation properties that would enable further composition of the query after your RawSQL is interpolated with the LINQ to SQL pipeline.
Once the type is defined in the context you simply call DbSet<T>.FromSqlRaw(). The difference is that now we have an IQueryable<T> that we can use to futher compose to include related entities or apply filters that will be evaluated within the database.
The solution posted in this response doesn't allow for composition, but uses the EF runtime in the expected sequence to give the same behaviours as the original EF 6 implementation.
In more recent versions of EF Core, and now in .Net 5+ the following subtle change need to be applied:
Core 2.1: return contextGeneric.Query<T>().FromSql(query, parameters).ToList();
Core 3+: return contextGeneric.Set<T>().FromSqlRaw(query, parameters).ToList();

You can use context.Database.ExecuteSqlRaw("select 1")
Don't forget to import the right namespace : using Microsoft.EntityFrameworkCore;

It worked like this:
private void SqlCommand (string connectionString, string query)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
try
{
while (reader.Read())
{
var a = reader[0];
}
}
finally
{
// Always call Close when done reading.
reader.Close();
}
}
}
Or
using (var connection = ContextFactory.GetNewContextGeneric(connectionString).Database.GetDbConnection())
{
connection.Open();
DbCommand command = connection.CreateCommand();
command.CommandText = query;
using (var reader = command.ExecuteReader())
{
// Do something with result
reader.Read(); // Read first row
var firstColumnObject = reader.GetValue(0);
/*var secondColumnObject = reader.GetValue(1);
reader.Read(); // Read second row
firstColumnObject = reader.GetValue(0);
secondColumnObject = reader.GetValue(1);*/
connection.Close();
return firstColumnObject.ToString();
}
}

Related

Asynchronous Database call to MySql Database gives error

I am using Microsoft.Enterprise Library with MySql database.
I am trying to call a database method asynchronously.
My method is like below..
public static async Task<DataTable> ExecuteDataSetWithParameterAsSourceAsync(this Database database, CommandDetail cmdDetail, params Object[] parameterSource)
{
List<int> outParamIndex = new List<int>();
AsyncCallback cb = new AsyncCallback(EndExecuteReaderCallBack);
DataTable dt = new DataTable();
DbAsyncState state = BeginExecuteReader(database, cmdDetail.CommandText, cb, parameterSource);
IDataReader reader = (IDataReader)state.State;
dt.Load(reader);
reader.Close();
...
return await Task.FromResult(dt);
}
I am getting below Error
{"The database type "MySqlDatabase" does not support asynchronous operations."}
Below is the complete stack image of the error..
My connection string is
<add name="VirtualCloudDB" providerName="EntLibContrib.Data.MySql" connectionString="database=test;uid=xxx;password=xxx;Data Source=test-instance; maximumpoolsize=3"/>
About the error
Oracle's Connector/.NET library didn't even allow asynchronous operations before v8.0. Even now, there are several quirks. It's better to use the independent, open-source MySqlConnector library.
If you absolutely must use Connector/.NET, upgrade to the latest version.
About the code (no history lesson)
Forget EntLib, especially DAAB. Even the docs say:
The Database class leverages the provider factory model from ADO.NET. A database instance holds a reference to a concrete DbProviderFactory object to which it forwards the creation of ADO.NET objects.
What you use isn't the real thing anyway, it's a community-supported clone of the official code that used to be stored in Codeplex. The only thing that is still in development is the Unity DI container.
Real async operations are available in ADO.NET and implemented by most providers. The database-agnostic, factory-based model of EntLib 1 was incorporated into ADO.NET 2 back in 2006. Entlib 2.0 DAAB is essentially a thin layer of convenience methods over ADO.NET 2.
ADO.NET 2 "Raw"
In ADO.NET 2.0 alone, the entire method can be replaced with :
async Task<DataTable> LoadProducts(string category)
{
var sql="select * from Products where category=#category";
using(var connection=new MySqlConnection(_connStrFromConfig))
using(var cmd=new MySqlCommand(sql,connection))
{
cmd.Parameters.AddWithValue("#category",category);
await connection.OpenAsync();
using(var reader=await cmd.ExecuteReaderAsync())
{
DataTable dt = new DataTable();
dt.Load(reader);
return dt;
}
}
}
Especially for MySQL, it's better to use the open-source MySqlConnector library than Oracle's official Connector/.NET.
ADO.NET 2 Factory model
ADO.NET 2 added abstract base classes and a factory model (based on DAAB 1, but easier) that allows using database-agnostic code as much as possible.
The previous code, without using the provider factory, can be rewritten as :
string _providerName="MySqlConnector"
DbCommand CreateConnection()
{
DbProviderFactory _factory =DbProviderFactories.GetFactory(_providerName);
connection = _factory.CreateConnection();
connection.ConnectionString = connectionString;
return connection;
}
async Task<DataTable> LoadProducts(string category)
{
var sql="select * from Products where category=#category";
using(DbConnection connection=CreateConnection())
using(DbCommand cmd= connection.CreateCommand())
{
cmd.CommandText=sql;
var param=cmd.CreateParameter();
param.Name="#category";
//The default is String, so we don't have to set it
//param.DbType=DbType.String;
param.Value=category;
cmd.Parameters.Add("#category",category);
await connection.OpenAsync();
using(var reader=await cmd.ExecuteReaderAsync())
{
DataTable dt = new DataTable();
dt.Load(reader);
return dt;
}
}
}
All that's needed to target eg SQL Server or Oracle is registering and using a different provider name.
The code can be simplified. For example, DbParameterCollection.AddRange can be used to add multiple parameters at once. That's still too much code by modern standards though.
Entlib 2 DAAB - it's the same classes
Entlib 2 DAAB uses the same abstract classes. In fact, the Database class does little more than add convenience methods on top of the abstract classes, eg methods to create a DbCommand, or execute a query and return a reader or a Dataset.
If you didn't need parameters, you could write just :
DataTable LoadProducts(Database database)
{
var sql="select * from Products";
var set=database.ExecuteDataSet(CommandType.Text,sql);
return set.Tables[0];
}
Unfortunately, there's no way to combine a raw query and parameters. Back when EntLib 1 was created it was thought that complex code should always be stored in a stored procedure. So while there's a ExecuteDataSet(string storedProcedureName,params object[] parameterValues), there's no equivalent for raw SQL.
And no Task-based async methods either. By 2010 EntLib was in support mode already.
Unfortunately again there's no way to directly create a DbCommand from Database. Again, the assumption was that people would either execute raw SQL or called a stored procedure. There's a GetSqlStringCommand that accepts no parameters. There's also Database.ProviderFactory that can be used to do everything manually, and end up with the same code as raw ADO.NET.
Another possible option is to cheat, and use Database.GetStoredProcCommand with positional parameters and change the CommandType
async Task<DataTable> LoadProducts(Database database,string category)
{
var sql="select * from Products where category=#category";
using(var cmd=database.GetStoredProcCommand(sql,category))
{
cmd.CommandType=CommandType.Text;
using(var reader=await cmd.ExecuteReaderAsync())
{
DataTable dt = new DataTable();
dt.Load(reader);
return dt;
}
}
return set.Tables[0];
}
Dapper
With microORM libraries like Dapper the code can be reduced to :
async Task<IEnumerable<Product>> LoadProducts(string category)
{
var sql="select * from Products where category=#category";
using(var connection=CreateConnection())
{
var products=await connection.Query<Product>(sql,new {category=category});
return products;
}
}
Dapper will open the connection if it's closed, execute the query asynchronously and map the results to the target object, in a single line of code. Parameters will be mapped by name from the parameter object.
When called without a type parameter, Query returns a list of dynamic objects
async Task<IEnumerable<dynamic>> LoadProducts(string category)
{
var sql="select * from Products where category=#category";
using(var connection=CreateConnection())
{
var products=await connection.Query(sql,new {category=category});
return products;
}
}

Share connection between DbContext objects in Entity Framework 6 Database first approach

I am trying to share a connection in between 2 different DB context objects using EF 6 .
I have got the code mentioned here
https://msdn.microsoft.com/en-us/data/dn456843.aspx
to work for a single DB Context but as soon as I try to share the connection with another DB Context object I am running into trouble. Below is what I have done so for.
public class MyUnitOfWork
{
// BAD CODE : having static connection / transaction is bad design , i use DI to implement this properly without need for static fields , this code is used here to avoid having to mention my DI configuration;
public static EntityConnection _commonConnection ;
public static System.Data.Entity.DbContextTransaction _commonTransaction;
// Generic function to create DBContext object based on T supplied.
public static T GetDbContext<T>()
{
if(_commonConnection == null)
{
// generates a generic connection string
_commonConnection = new EntityConnection(DbContextBase.GenerateConnectionString());
_connection.Open();
_commonTransaction = _connection.BeginTransaction(System.Data.IsolationLevel.Snapshot);
}
T myContextObject = (T)Activator.CreateInstance(typeof(T), new object[1] { _connection });
myContextObject .Database.UseTransaction(_transaction);
return myContextObject;
}
}
The code for generation of connection string is mentioned below:
string GenerateConnectionString()
{
var entityBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
ProviderConnectionString = SharedDatabase.SQLBase.DBconnection + "multipleactiveresultsets=True;" ,
Metadata = #"res://*/;"
};
return entityBuilder ;
}
I can then call the GetDbContext from different places in my function like so
GetById(int id)
{
var dbcontext = MyUnitOfWork.GetDbContext<OrdersDbContext>();
return dbcontext.Orders.Where(.......);
}
GetCustomerInfo( int id)
{
var dbcontext = MyUnitOfWork.GetDbContext<CustomerDbContext>();
return dbcontext.Customer.Where(.......);
}
Because I am using the generic metadata portion , I am getting errors in entity framework "cannot have more than one entity with same name regardless of the namespace"
However if I specify the name of the .csdl/.ssdl file , the connection ( and hence the transaction ) can no more be common and I have to create a connection and Transaction for each DBContext ( this is what i wanted to avoid )
It seems I have hit a block . Is there a way for me to use the same connection without getting the duplicate entities error ? Changing the names of entities to be different is not an option for me as it will be a very time consuming change which I would have to do across 30 + db context / EDMX files with huge production impact.
As seen in Working with Transactions (EF6 Onwards), use the following code to provide an existing transaction to your context :
string GenerateConnectionString()
{
return SharedDatabase.SQLBase.DBconnection + "multipleactiveresultsets=True;";
}
public class MyUnitOfWork
{
SqlConnection _commonConnection;
DbTransaction _commonTransaction;
// Generic function to create DBContext object based on T supplied.
public T GetDbContext<T>()
{
if (_commonConnection == null)
{
// generates a generic connection string
_commonConnection = new SqlConnection(DbContextBase.GenerateConnectionString());
_commonConnection.Open();
_commonTransaction = _connection.BeginTransaction(IsolationLevel.Snapshot);
}
MetadataWorkspace workspace = new MetadataWorkspace(
string.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;", typeof(T).Name).Split('|'),
new Assembly[] { Assembly.GetExecutingAssembly() });
var connection = new EntityConnection(workspace, _commonConnection);
T myContextObject = (T)Activator.CreateInstance(typeof(T), new object[] { connection });
myContextObject.Database.UseTransaction(_commonTransaction);
return myContextObject;
}
}

writing more optimized code and reusing code in C#

I'm writing a desktop application using C# winforms and MSSQL server 2012. there are several classes exit in this application that need to connect to database and all uisng ADO.Net. this is on of my classes :
class Prices
{
private int id = 0;
public int Id
{
get { return id; }
set { id = value; }
}
private string materialName = string.Empty;
......
......
......
public void updateMaterialPrice()
{
string conString = ConfigurationManager.ConnectionStrings["secaloFormulaCS"].ToString();
using (SqlConnection sqlCon = new SqlConnection(conString))
using (SqlCommand sqlCmd = new SqlCommand("spUpdateMaterialPrice", sqlCon))
{
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("materialName",MaterialName);
sqlCmd.Parameters.AddWithValue("unitPrice", Price);
sqlCmd.Parameters.AddWithValue("carbohydrate", Carbohydrtate);
sqlCmd.Parameters.AddWithValue("protein", Proterin);
sqlCmd.Parameters.AddWithValue("fat", Fat);
sqlCmd.Parameters.AddWithValue("humidity", Humadity);
sqlCmd.Parameters.AddWithValue("minerlas", Minerlas);
sqlCon.Open();
sqlCmd.ExecuteNonQuery();
sqlCon.Close();
sqlCon.Dispose();
}
}
public void addMaterial()
{
string ConString = ConfigurationManager.ConnectionStrings["secaloFormulaCS"].ToString();
using(SqlConnection sqlCon = new SqlConnection(ConString))
using(SqlCommand sqlCmd = new SqlCommand("spAddMaterial",sqlCon))
{
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("materialName", MaterialName);
sqlCmd.Parameters.AddWithValue("unitPrice",Price);
sqlCmd.Parameters.AddWithValue("carbohydrate",Carbohydrtate);
sqlCmd.Parameters.AddWithValue("proterin", Proterin);
sqlCmd.Parameters.AddWithValue("fat",Fat);
sqlCmd.Parameters.AddWithValue("humidity", Humadity);
sqlCmd.Parameters.AddWithValue("minerals",Minerlas);
sqlCon.Open();
sqlCmd.ExecuteNonQuery();
sqlCon.Close();
sqlCon.Dispose();
}
as you can see in addMterial() and updateMaterialPrice() i use the same code to connect to database and call a stored procedure and this is repeated for several times in my other classes. how can i prevent this code repetition ? is it any way to just write the code needed for connection and query the database one time and reuse it several times according to situation needed ?
I use a Factory pattern for my Database connections, this means I never have to open a SqlConnection or pass connection strings around my program.
Here is an example for the method I use to run a query that returns multiple rows.
I would call the method from a "makeObject" method that would turn this datatable into an object.
public static class DB
{
private static readonly string connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
private static readonly DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
/// <summary>
/// Use when returning data from multiple rows
/// </summary>
/// <param name="sql">query</param>
/// <param name="parameters">declared parameters</param>
/// <returns>datatable of db rows</returns>
public static DataTable GetDataTable(string sql, SqlParameter[] parameters)
{
try
{
using (DbConnection connection = factory.CreateConnection())
{
connection.ConnectionString = connectionString;
using (DbCommand command = factory.CreateCommand())
{
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = sql;
if (parameters != null)
{
foreach (var parameter in parameters)
{
if (parameter != null)
command.Parameters.Add(parameter);
}
}
using (DbDataAdapter adapter = factory.CreateDataAdapter())
{
adapter.SelectCommand = command;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}
}
catch (Exception)
{
throw;
}
}
}
Well, you could make a helper method to prepare the command for you, or at least fill the parameters, eg.
void PrepareParameters(SqlCommand cmd)
{
cmd.Parameters.AddWithValue("materialName",MaterialName);
cmd.Parameters.AddWithValue("unitPrice", Price);
cmd.Parameters.AddWithValue("carbohydrate", Carbohydrtate);
cmd.Parameters.AddWithValue("protein", Proterin);
cmd.Parameters.AddWithValue("fat", Fat);
cmd.Parameters.AddWithValue("humidity", Humadity);
cmd.Parameters.AddWithValue("minerlas", Minerlas);
}
Ideally, unless you want to use a ready ORM like Entity Framework (usually a good idea), you'd create a few abstract classes to handle these kinds of things, so that you'll save on code reuse.
For example, the PrepareParameters method could be abstract, and there could be an abstract property that returns the name of the SP to update, create or delete (or better yet, you could follow a naming scheme so that you'd only need one name). Then you could write 99% of the logic in the abstract base classes, and only prepare the parameters in the actual derived classes, thus cutting code repetition a lot.
Some options are as follows:
Write a SqlHelper class which does the repetitive grunt work of executing a stored procedure. (especially ExecuteNonQuery ones, so that you don't need to worry about return types.)
e.g.
public void ExecuteQuery(string sprocName, SqlParamater[] parameters)
{
// initialize connection
// construct command with sprocName and parameters
// ExecuteNonQuery
}
Use Linq2Sql
This is an excellent quick ORM tool which simplies data access to a great deal.
Use Entity Framework
This is an increasingly used ORM tool.
All of the above approaches have their pros/cons. You need to weight them & select the right method.

What context to use in order to run raw query on simplemembership tables MVC 4 EF6

I'm new to .Net MVC and I wanted to run a raw query on my UserInRoles table.
I think I will need a database context to run it.
I'm unsure what context to use. Can some one recommend me a direction to take? Currently, the ObjectContext does not allow me to instantiate without a connection string. Is directly grabbing the connection string from web config correct?
Error 1 'System.Data.Entity.Core.Objects.ObjectContext' does not contain a constructor that takes 0 arguments
using (var ctx = new ObjectContext())
{
string query = "INSERT INTO dbo.webpages_UsersInRoles (RoleId,UserId) values ("+chk+","+id+");";
ExecuteSql(ctx,query);
}
ExecuteSql is using ADO.net connections different from what EF recommends but I need to do this manual insert in order for this section to work.
static void ExecuteSql(ObjectContext c, string sql)
{
var entityConnection = (System.Data.EntityClient.EntityConnection)c.Connection;
DbConnection conn = entityConnection.StoreConnection;
ConnectionState initialState = conn.State;
try
{
if (initialState != ConnectionState.Open)
conn.Open(); // open connection if not already open
using (DbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
finally
{
if (initialState != ConnectionState.Open)
conn.Close(); // only close connection if not initially open
}
}
The ObjectContext (MSDN Link) requires you to provide a connection string to execute against. The ObjectContext is not your EF container but a context to query against a database and transform to objects. Because it is a generic query mechanism it does not know which connection string to use. In addition, your query is a simple insert query making the ObjectContext the wrong tool for the job. As the ObjectContext provides mapping mechanisms that you will not require I would suggest using your EF container to execute the query. This can be done by calling (sample code)
dbContainer.Database.ExecuteSqlCommand(query)
This command will grab your existing connection string from your configuration file and execute the query provided. Finally there is another option if you really want a ObjectContext without having to enter the connection string. You can do this by providing a wrapper, or static method to create an ObjectContext without the connection string. Such as.
Method 1: Wrapper (inheritance)
class MyObjectContext : ObjectContext
{
public MyObjectContext()
: base(MyObjectContext.connectionString)
{ }
/// <summary>
/// the connection string id in the config
/// </summary>
const string connectionStringID = "dbCon";
/// <summary>
/// gets the connection string
/// </summary>
static string connectionString
{
get
{
return ConfigurationManager.ConnectionStrings[connectionStringID].ConnectionString
}
}
}
Method 2. Static Property (or method)
static ObjectContext New
{
get
{
return new ObjectContext(ConfigurationManager.ConnectionStrings["dbCon"].ConnectionString);
}
}
Now personally I prefer method 1 (if i had to do this) as it also gives me the ability to extend this class to define my queries in one class such as.
class MyObjectContext : ObjectContext
{
//.....
public void Insert_UserInRole(string roleID, string id)
{
///TODO DO: insert role
}
//.....
}
And can be called such as.
using (var context = new MyObjectContext())
{
context.Insert_UserInRole("abc", "123");
}
[Just a thought]
Important In addition your query is begging for SQL Injection. Use "parameters" in your queries. Using parameters will prevent SQL inject atacks. Here is a SO topic regarding parameters. How to pass parameters to the DbContext.Database.ExecuteSqlCommand method?
I hope this helps.

What's the most DRY-appropriate way to execute an SQL command?

I'm looking to figure out the best way to execute a database query using the least amount of boilerplate code. The method suggested in the SqlCommand documentation:
private static void ReadOrderData(string connectionString)
{
string queryString = "SELECT OrderID, CustomerID FROM dbo.Orders;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(queryString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
try
{
while (reader.Read())
{
Console.WriteLine(String.Format("{0}, {1}", reader[0], reader[1]));
}
}
finally
{
reader.Close();
}
}
}
mostly consists of code that would have to be repeated in every method that interacts with the database.
I'm already in the habit of factoring out the establishment of a connection, which would yield code more like the following. (I'm also modifying it so that it returns data, in order to make the example a bit less trivial.)
private SQLConnection CreateConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
private List<int> ReadOrderData()
{
using(var connection = CreateConnection())
using(var command = connection.CreateCommand())
{
command.CommandText = "SELECT OrderID FROM dbo.Orders;";
using(var reader = command.ExecuteReader())
{
var results = new List<int>();
while(reader.Read()) results.Add(reader.GetInt32(0));
return results;
}
}
}
That's an improvement, but there's still enough boilerplate to nag at me. Can this be reduced further? In particular, I'd like to do something about the first two lines of the procedure. I don't feel like the method should be in charge of creating the SqlCommand. It's a tiny piece of repetition as it is in the example, but it seems to grow if transactions are being managed manually or timeouts are being altered or anything like that.
edit: Assume, at least hypothetically, there's going to have to be a bunch of different types of data being returned. And consequently the solution can't be just one one-size-fits-all method, there will have to be a few different ones depending, at minimum, on whether ExecuteNonQuery, ExecuteScalar, ExecuteReader, ExecuteReaderAsync, or any of the others are being called. I'd like to cut down on the repetition among those.
Tried Dapper?
Granted this doesn't get you a DataReader but you might just prefer it this way once you've tried it.
It's about the lightest-weight an ORM can be while still being called an ORM. No more methods to map between DataReader and strong types for me.
Used right here on all the StackExchange sites.
using (var conn = new SqlConnection(cs))
{
var dogs = connection.Query("select name, age from dogs");
foreach (dynamic dog in dogs)
{
Console.WriteLine("{0} age {1}", dog.name, dog.age);
}
}
or
using (var conn = new SqlConnection(cs))
{
var dogs = connection.Query<Dog>("select Name, Age from dogs");
foreach (Dog dog in dogs)
{
Console.WriteLine("{0} age {1}", dog.Name, dog.Age);
}
}
class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
If you want to roll data access on your own, this pattern of help methods could be one way to remove duplication:
private List<int> ReadOrderData()
{
return ExecuteList<int>("SELECT OrderID FROM dbo.Orders;",
x => x.GetInt32("orderId")).ToList();
}
private IEnumerable<T> ExecuteList(string query,
Func<IDataRecord, T> entityCreator)
{
using(var connection = CreateConnection())
using(var command = connection.CreateCommand())
{
command.CommandText = query;
connection.Open();
using(var reader = command.ExecuteReader())
{
while(reader.Read())
yield return entityCreator(reader);
}
}
}
You'll have to add support for parameters and this might not compile, but the pattern is what I'm trying to illustrate.
What I typically do is use a custom class that I wrote a while back that accepts a SQL string, and optionally a list of parameters and it returns a DataTable.
Since the thing that changes between invocations is typically just the SQL that is optimal IMHO.
If you truly do need to use a DataReader you can do something like this:
public void ExecuteWithDataReader(string sql, Action<DataReader> stuffToDo) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
using (SqlCommand command = new SqlCommand(sql, connection)) {
connection.Open();
using (SqlDataReader reader = command.ExecuteReader()) {
try {
while (reader.Read()) {
stuffToDo(reader);
}
}
finally {
reader.Close();
}
}
}
}
}
private static void ReadOrderData(string connectionString) {
string sql = "SELECT OrderID, CustomerID FROM dbo.Orders;";
ExecuteWithDataReader(sql, r => Console.WriteLine(String.Format("{0}, {1}", r[0], r[1])));
}
The first two line are the most important thing you need...
but if you still wish to do it, you can turn them to a database handler class, yes it will become more of code, but in refactoring concept, every thing will move to the related topic...
try to write a singleton class, that receive a command and do action, so return result of type SqlDataReader reader...
Doing this in comments was too much.
I would suggest that the boilerplate code around
using(conn = new sqlconnection)
using(cmd = new sqlcommand) {
// blah blah blah
}
isn't something to be lightly removed and instead would encourage that you keep it exactly where it's at. Resources, especially unmanaged ones, should be opened and released at the closest point to execution as possible IMHO.
In no small part due to the ease with which other developers will fail to follow the appropriate clean up conventions.
If you do something like
private SQLConnection CreateConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
Then you are inviting another programmer to call this method and completely fail to release the resource as soon as the query is executed. I don't know what kind of app you are building, but in a web app such a thing will lead to memory / connection / resource errors of types that are difficult to debug, unless you've been through it before.
Instead, I'd suggest you look into a lightweight ORM such as Dapper.net or similar to see how they approached it. I don't use dapper, but I hear it's pretty good. The reason I don't use it is simply that we don't allow inline sql to be executed against our databases (but that's a very different conversation).
Here's our standard:
public static DataTable StatisticsGet( Guid tenantId ) {
DataTable result = new DataTable();
result.Locale = CultureInfo.CurrentCulture;
Database db = DatabaseFactory.CreateDatabase(DatabaseType.Clients.ToString());
using (DbCommand dbCommand = db.GetStoredProcCommand("reg.StatsGet")) {
db.AddInParameter(dbCommand, "TenantId", DbType.Guid, tenantId);
result.Load(db.ExecuteReader(dbCommand));
} // using dbCommand
return result;
} // method::StatisticsGet
We make heavy use of Enterprise Library. It's short, simple and to the point and very well tested. This method just returns a datatable but you could easily have it return an object collection.. or nothing.

Categories