I am replacing a very long sql statement in C# with a stored procedure. The sql was called by the FromSqlRaw function. What piece of EF functionality can be implemented to achieve the same result. The following returns an exception error near b6f42 or similar each time I run the program. The procedure results called from SSMS are satisfactory.
var wrk = DbCtx.WRK.FromSqlRaw($"Execute GetCustomersAsync
{Id} {LastName}").ToObservableCollection();
You could use SqlQuery for that:
var param1 = new SqlParameter()
{
ParameterName = "#Date",
SqlDbType = SqlDbType.DateTime,
Value = validityEnd
};
var parameters = new SqlParameter[] { param1 };
return context
.Set<TEntity>()
.SqlQuery<ReturnType>(
"[schema].[spName] #Date",
parameters);
Related
I need to know the correct way to handle SQL Injection when using the FromSQL command.
At runtime, I need to dynamically create a where statement. So I am using FromSql to create the SQL command. Now I know that using use string interpolation is the way to go. However, I need to step through a list of "Where Parameters" to generate the command. Simple enough to do;
foreach (var x in wp)
{
if (!string.IsNullOrEmpty(results))
results = $"{results} and {x.Field} = {x.Value}";
if (string.IsNullOrEmpty(results))
results = $"where {x.Field} = {x.Value}";
}
Problem is that this return a simple string and would not be string interpolation. How can I do this correctly?
Entityframework will parameterize your queries if you put it in the following format:
db.something.FromSql("SELECT * FROM yourTable WHERE AuthorId = {0}", id)
Is x.Field a form field that has a fixed number of possibilities? i.e. title, firstname etc. If so then something like the following:
var sqlstring = new StringBuilder();
var sqlp = new List<SqlParameter>();
var i = 0;
foreach (var x in wp)
{
var param = "#param" + i.ToString();
if (i!=0)
{
sqlstring.Append($" AND {x.Field} = " + param);
sqlp.Add(new SqlParameter(param, x.Value));
}
if (i==0)
{
sqlstring.Append($"WHERE {x.Field} = " + " #param" + i.ToString());
sqlp.Add(new SqlParameter(param, x.Value));
}
i++;
}
You'd then need to do something like this:
db.something.FromSql(sqlstring.ToString(), sqlp.ToArray())
Might be a better/cleaner way but that should work.
My solution to this problem is a VS extension, QueryFirst. QueryFirst generates a C# wrapper for sql that lives in a .sql file. As such, parameters are the only way to get data into your query and SQL injection is near impossible. There are numerous other advantages: you edit your sql in a real environment, it's constantly validated against your db, and using your query in your code is very simple.
I'm trying to use repository pattern for my vsto project.
How do I use a repository pattern to execute a stored procedure? I'm using Entity Framework. Any link for code sample would really useful
To your generic repository add
public IEnumerable<T> ExecWithStoreProcedure(string query, params object[] parameters)
{
return _context.Database.SqlQuery<T>(query, parameters);
}
And then you can call it with any unitofwork/repository like
IEnumerable<Products> products =
_unitOfWork.ProductRepository.ExecWithStoreProcedure(
"spGetProducts #bigCategoryId",
new SqlParameter("bigCategoryId", SqlDbType.BigInt) { Value = categoryId }
);
A non generic solution in your repository would be:
private int ExecWithStoreProcedure(string query, params object[] parameters)
{
return _context.Database.ExecuteSqlCommand("EXEC " + query, parameters);
}
And then a few typical examples of use:
var param = new SqlParameter("SomethingToCheck", SqlDbType.NVarChar) { Value = shortCode };
var result = ExecWithStoreProcedure("mySchema.myStoredProc #SomethingToCheck", param);
with multiple parameters:
var param1 = new SqlParameter("SomeCode", SqlDbType.VarChar) { Value = shortCode };
var param2 = new SqlParameter("User", SqlDbType.VarChar) { Value = userName };
var result = ExecWithStoreProcedure("mySchema.myStoredProc #SomeCode, #User", param1, param2 );
this link guided me. [Link]
But when you execute stored procedure you have to put "exec" informant of SP name
Eg: if sp is "sp_aa"
string should be "exec sp_aa"
trying to execute the stored proc in EF using the following code:
var params = new object[] {new SqlParameter("#FirstName", "Bob")};
return this._repositoryContext.ObjectContext.ExecuteStoreQuery<ResultType>("GetByName", params);
but keep getting this error:
Procedure or function 'GetByName' expects parameter '#FirstName',
which was not supplied.
and from sql profiler:
exec sp_executesql N'GetByName',N'#FirstName nvarchar(100),#FirstName=N'Bob'
what is wrong wit the above ExecuteStoreQuery code?
Ignoring the fact that params is a reserved word...
Think your query needs to be:
var params = new object[] {new SqlParameter("#FirstName", "Bob")};
return this._repositoryContext.ObjectContext.ExecuteStoreQuery<ResultType>("exec GetByName #FirstName", params);
Should also say that if that proc is a standard part of your database and data model then you should import it into your EDM so it's available directly on your context.
Use the ExecuteFunction instead of ExecuteStoreQuery which is more suitable for the "ad-hoc" queries.
var parameters = new ObjectParameter[] {new ObjectParameter("FirstName", "Bob")};
return this._repositoryContext.ObjectContext.ExecuteFunction<ResultType>("GetByName", parameters);
The stored procedures can also be mapped as function in the context and thus can be used as typed method. Take a look at Using stored procedures with Entity Framework.
This is what I did to use a SP in EF, if you have multiple parameters:-
public virtual ObjectResult<GetEpisodeCountByPracticeId_Result> GetEpisodeCountByPracticeId(Nullable<int> practiceId, Nullable<System.DateTime> dat1)
{
SqlParameter practiceIdParameter = practiceId.HasValue ?
new SqlParameter() { ParameterName = "practiceId", Value = practiceId, SqlDbType = SqlDbType.Int } :
new SqlParameter() { ParameterName = "practiceId", SqlDbType = SqlDbType.Int };
SqlParameter dat1Parameter = dat1.HasValue ?
new SqlParameter() { ParameterName = "dat1", Value = dat1, SqlDbType = SqlDbType.DateTime }:
new SqlParameter() { ParameterName = "dat1", SqlDbType = SqlDbType.DateTime };
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<GetEpisodeCountByPracticeId_Result>("exec GetEpisodeCountByPracticeId #practiceId, #dat1", practiceIdParameter, dat1Parameter);
}
If you dont add the parameters (e.g. #practiceId) in the commandText property then you get the error you received
I am working with a system that has many stored procedures that need to be displayed. Creating entities for each of my objects is not practical.
Is it possible and how would I return a DataTable using ExecuteStoreQuery ?
public ObjectResult<DataTable> MethodName(string fileSetName) {
using (var dataContext = new DataContext(_connectionString))
{
var returnDataTable = ((IObjectContextAdapter)dataContext).ObjectContext.ExecuteStoreQuery<DataTable>("SP_NAME","SP_PARAM");
return returnDataTable;
}
Yes it's possible, but it should be used for just dynamic result-set or raw SQL.
public DataTable ExecuteStoreQuery(string commandText, params Object[] parameters)
{
DataTable retVal = new DataTable();
retVal = context.ExecuteStoreQuery<DataTable>(commandText, parameters).FirstOrDefault();
return retVal;
}
Edit: It's better to use classical ADO.NET to get the data model rather than using Entity Framework because most probably you cannot use DataTable even if you can run the method: context.ExecuteStoreQuery<DataTable>(commandText, parameters).FirstOrDefault();
ADO.NET Example:
public DataSet GetResultReport(int questionId)
{
DataSet retVal = new DataSet();
EntityConnection entityConn = (EntityConnection)context.Connection;
SqlConnection sqlConn = (SqlConnection)entityConn.StoreConnection;
SqlCommand cmdReport = new SqlCommand([YourSpName], sqlConn);
SqlDataAdapter daReport = new SqlDataAdapter(cmdReport);
using (cmdReport)
{
SqlParameter questionIdPrm = new SqlParameter("QuestionId", questionId);
cmdReport.CommandType = CommandType.StoredProcedure;
cmdReport.Parameters.Add(questionIdPrm);
daReport.Fill(retVal);
}
return retVal;
}
No, I don't think that'll work - Entity Framework is geared towards returning entities and isn't meant to return DataTable objects.
If you need DataTable objects, use straight ADO.NET instead.
This method uses the connection string from the entity framework to establish an ADO.NET connection, to a MySQL database in this example.
using MySql.Data.MySqlClient;
public DataSet GetReportSummary( int RecordID )
{
var context = new catalogEntities();
DataSet ds = new DataSet();
using ( MySqlConnection connection = new MySqlConnection( context.Database.Connection.ConnectionString ) )
{
using ( MySqlCommand cmd = new MySqlCommand( "ReportSummary", connection ) )
{
MySqlDataAdapter adapter = new MySqlDataAdapter( cmd );
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
adapter.SelectCommand.Parameters.Add( new MySqlParameter( "#ID", RecordID ) );
adapter.Fill( ds );
}
}
return ds;
}
Yes it can easily be done like this:
var table = new DataTable();
using (var ctx = new SomeContext())
{
var cmd = ctx.Database.Connection.CreateCommand();
cmd.CommandText = "Select Col1, Col2 from SomeTable";
cmd.Connection.Open();
table.Load(cmd.ExecuteReader());
}
By the rule, you shouldn't use a DataSet inside a EF application. But, if you really need to (for instance, to feed a report), that solution should work (it's EF 6 code):
DataSet GetDataSet(string sql, CommandType commandType, Dictionary<string, Object> parameters)
{
// creates resulting dataset
var result = new DataSet();
// creates a data access context (DbContext descendant)
using (var context = new MyDbContext())
{
// creates a Command
var cmd = context.Database.Connection.CreateCommand();
cmd.CommandType = commandType;
cmd.CommandText = sql;
// adds all parameters
foreach (var pr in parameters)
{
var p = cmd.CreateParameter();
p.ParameterName = pr.Key;
p.Value = pr.Value;
cmd.Parameters.Add(p);
}
try
{
// executes
context.Database.Connection.Open();
var reader = cmd.ExecuteReader();
// loop through all resultsets (considering that it's possible to have more than one)
do
{
// loads the DataTable (schema will be fetch automatically)
var tb = new DataTable();
tb.Load(reader);
result.Tables.Add(tb);
} while (!reader.IsClosed);
}
finally
{
// closes the connection
context.Database.Connection.Close();
}
}
// returns the DataSet
return result;
}
In my Entity Framework based solution I need to replace one of my Linq queries with sql - for efficiency reasons.
Also I want my results in a DataTable from one stored procedure so that I could create a table value parameter to pass into a second stored procedure. So:
I'm using sql
I don't want a DataSet
Iterating an IEnumerable probably isn't going to cut it - for efficiency reasons
Also, I am using EF6, so I would prefer DbContext.SqlQuery over ObjectContext.ExecuteStoreQuery as the original poster requested.
However, I found that this just didn't work:
_Context.Database.SqlQuery<DataTable>(sql, parameters).FirstOrDefault();
This is my solution. It returns a DataTable that is fetched using an ADO.NET SqlDataReader - which I believe is faster than a SqlDataAdapter on read-only data. It doesn't strictly answer the question because it uses ADO.Net, but it shows how to do that after getting a hold of the connection from the DbContext
protected DataTable GetDataTable(string sql, params object[] parameters)
{
//didn't work - table had no columns or rows
//return Context.Database.SqlQuery<DataTable>(sql, parameters).FirstOrDefault();
DataTable result = new DataTable();
SqlConnection conn = Context.Database.Connection as SqlConnection;
if(conn == null)
{
throw new InvalidCastException("SqlConnection is invalid for this database");
}
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddRange(parameters);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
result.Load(reader);
}
return result;
}
}
The easiest way to return a DataTable using the EntityFramework is to do the following:
MetaTable metaTable = Global.DefaultModel.GetTable("Your EntitySetName");
For example:
MetaTable metaTable = Global.DefaultModel.GetTable("Employees");
Maybe your stored procedure could return a complex type?
http://blogs.msdn.com/b/somasegar/archive/2010/01/11/entity-framework-in-net-4.aspx
So here's the deal. In our database, we wrap most of our reads (i.e. select statements) in table valued functions for purposes of security and modularity. So I've got a TVF which defines one or more optional parameters.
I believe having a TVF with defaulted parameters mandates the use of the keyword default when calling the TVF like so:
select * from fn_SampleTVF(123, DEFAULT, DEFAULT)
That's fine, everything works in the query analyzer, but when it comes time to actually make this request from ADO.NET, I'm not sure how to create a sql parameter that actually puts the word default into the rendered sql.
I have something roughly like this now:
String qry = "select * from fn_SampleTVF(#requiredParam, #optionalParam)";
DbCommand command = this.CreateStoreCommand(qry, CommandType.Text);
SqlParameter someRequiredParam = new SqlParameter("#requiredParam", SqlDbType.Int);
someRequiredParam.Value = 123;
command.Parameters.Add(someRequiredParam);
SqlParameter optionalParam = new SqlParameter("#optionalParam", SqlDbType.Int);
optionalParam.Value = >>>> WTF? <<<<
command.Parameters.Add(optionalParam);
So, anybody got any ideas how to pass default to the TVF?
SqlParameter optionalParam = new SqlParameter("#optionalParam", SqlDbType.Int);
optionalParam.Value = >>>> WTF? <<<<
command.Parameters.Add(optionalParam);
You don't have to add above code (The optional parameter) for default. SQL Server will use the default as defined in your UDF. However if you would like to pass different value then you can pass:
SqlParameter optionalParam = new SqlParameter("#optionalParam", SqlDbType.Int);
optionalParam.Value = newValue;
command.Parameters.Add(optionalParam);
I would have done so:
public void YourMethod(int rparam, int? oparam = null)
{
String qry = string.Format("select * from fn_SampleTVF(#requiredParam, {0})"
, !oparam.HasValue ? "default" : "#optionalParam");
SqlParameter someRequiredParam = new SqlParameter("#requiredParam", SqlDbType.Int);
someRequiredParam.Value = rparam;
command.Parameters.Add(someRequiredParam);
if (oparam.HasValue)
{
SqlParameter optionalParam = new SqlParameter("#optionalParam", SqlDbType.Int);
optionalParam.Value = oparam.Value;
command.Parameters.Add(optionalParam);
}
}
You can pass Null as the parameter value.
This article shows examples: http://support.microsoft.com/kb/321902