EF Core FromSqlRaw not executing stored procedure - entity-framework-core

I am using EF Core Version 3.1.9.
The code shown here is to execute a stored procedure - but it does not seem to work:
var test1 = "abc";
var test2 = "xyz";
EmpResponse emp = await _context.Emp
.FromSqlRaw("EXECUTE EMPS..sel_keys #param1 = 20000, #param2 = {0}, #param3 = {1}", test1, test2)
.FirstOrDefaultAsync();
I get this error:
FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it.
Consider calling AsEnumerable after the FromSqlRaw or FromSqlInterpolated method to perform the composition on the client side.
I found the below document and it suggest to modify using AsEnumerable().FirstOrDefault()
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#fromsql
But since I am using await, the above suggestion does not work.
Could someone suggest how to do it with await?

The simplest is to await a call to ToListAsync() and then apply {First|Single|Last}{OrDefault}, e.g.
var emp = (await _context.Emp
.FromSqlRaw("EXECUTE EMPS..sel_keys #param1 = 20000, #param2 = {0}, #param3 = {1}", test1, test2)
.ToListAsync())
.FirstOrDefault();
In general they (EF Core designers) are expecting you to just insert AsAsyncEnumerable()
var emp = await _context.Emp
.FromSqlRaw("EXECUTE EMPS..sel_keys #param1 = 20000, #param2 = {0}, #param3 = {1}", test1, test2)
.AsAsyncEnumerable() // <--
.FirstOrDefaultAsync();
but that first requires installing additionally System.Linq.Async package, and second - once you do that, you start getting a lot of ambiguous call compile-time errors when you access DbSets in queries, because they implement both IQueryable<T> and IAsyncEnumerable<T>.
So you'd better stay with the first solution until this gets resolved (I think it would be in EF Core 6).

Related

Entity Framework, calling a procedure through FromSqlRaw()

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

how to call stored procedure with parameter in entity framework

I am getting below error when I am calling procedure from entity framework with parameter
alter proc GetResultsForCampaign
#aa int
as
begin
SELECT aa from xyz
end
calling code
var result = context.Database.SqlQuery("GetResultsForCampaign #aa", 22).ToList();
error must declare the variable #aa
You can try this.
using Microsoft.Data.SqlClient;
var result = await db.Context
.FromSqlRaw("exec [dbo].[your_sp] #param1, #param2",
new SqlParameter("param1", value1),
new SqlParameter("param2", value2))
.AsNoTracking() // optional
.ToListAsync()

Batch update/delete EF5

What is the best way to deal with batch updates using (Entity Framework) EF5?
I have 2 particular cases I'm interested in:
Updating a field (e.g. UpdateDate) for a list (List) of between 100 and 100.000 Id's, which the primary key. Calling each update separately seem to be to much overhead and takes a long time.
Inserting many, also between the 100 and 100.000, of the same objects (e.g. Users) in a single go.
Any good advice?
There are two open source projects allowing this: EntityFramework.Extended and Entity Framework Extensions. You can also check discussion about bulk updates on EF's codeplex site.
Inserting 100k records through EF is in the first place wrong application architecture. You should choose different lightweight technology for data imports. Even EF's internal operation with such big record set will cost you a lot of processing time. There is currently no solution for batch inserts for EF but there is broad discussion about this feature on EF's code plex site.
I see the following options:
1 . The simplest way - create your SQL request by hands and execute through ObjectContext.ExecuteStoreCommand
context.ExecuteStoreCommand("UPDATE TABLE SET FIELD1 = {0} WHERE FIELD2 = {1}", value1, value2);
2 . Use EntityFramework.Extended
context.Tasks.Update(
t => t.StatusId == 1,
t => new Task {StatusId = 2});
3 . Make your own extension for EF. There is an article Bulk Delete where this goal was achieved by inheriting ObjectContext class. It's worth to take a look. Bulk insert/update can be implemented in the same way.
You may not want to hear it, but your best option is to not use EF for bulk operations. For updating a field across a table of records, use an Update statement in the database (possibly called through a stored proc mapped to an EF Function). You can also use the Context.ExecuteStoreQuery method to issue an Update statement to the database.
For massive inserts, your best bet is to use Bulk Copy or SSIS. EF will require a separate hit to the database for each row being inserted.
Bulk inserts should be done using the SqlBulkCopy class. Please see pre-existing StackOverflow Q&A on integrating the two: SqlBulkCopy and Entity Framework
SqlBulkCopy is a lot more user-friendly than bcp (Bulk Copy command-line utility) or even OPEN ROWSET.
Here's what I've done successfully:
private void BulkUpdate()
{
var oc = ((IObjectContextAdapter)_dbContext).ObjectContext;
var updateQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters.
var updateParams = GetSqlParametersForIQueryable(updateQuery).ToArray();
var updateSql = $#"UPDATE dbo.myTable
SET col1 = x.alias2
FROM dbo.myTable
JOIN ({updateQuery}) x(alias1, alias2) ON x.alias1 = dbo.myTable.Id";
oc.ExecuteStoreCommand(updateSql, updateParams);
}
private void BulkInsert()
{
var oc = ((IObjectContextAdapter)_dbContext).ObjectContext;
var insertQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters.
var insertParams = GetSqlParametersForIQueryable(insertQuery).ToArray();
var insertSql = $#"INSERT INTO dbo.myTable (col1, col2)
SELECT x.alias1, x.alias2
FROM ({insertQuery}) x(alias1, alias2)";
oc.ExecuteStoreCommand(insertSql, insertParams.ToArray());
}
private static IEnumerable<SqlParameter> GetSqlParametersForIQueryable<T>(IQueryable<T> queryable)
{
var objectQuery = GetObjectQueryFromIQueryable(queryable);
return objectQuery.Parameters.Select(x => new SqlParameter(x.Name, x.Value));
}
private static ObjectQuery<T> GetObjectQueryFromIQueryable<T>(IQueryable<T> queryable)
{
var dbQuery = (DbQuery<T>)queryable;
var iqProp = dbQuery.GetType().GetProperty("InternalQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var iq = iqProp.GetValue(dbQuery, null);
var oqProp = iq.GetType().GetProperty("ObjectQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
return (ObjectQuery<T>)oqProp.GetValue(iq, null);
}
public static bool BulkDelete(string tableName, string columnName, List<object> val)
{
bool ret = true;
var max = 2000;
var pages = Math.Ceiling((double)val.Count / max);
for (int i = 0; i < pages; i++)
{
var count = max;
if (i == pages - 1) { count = val.Count % max; }
var args = val.GetRange(i * max, count);
var cond = string.Join("", args.Select((t, index) => $",#p{index}")).Substring(1);
var sql = $"DELETE FROM {tableName} WHERE {columnName} IN ({cond}) ";
ret &= Db.ExecuteSqlCommand(sql, args.ToArray()) > 0;
}
return ret;
}
I agree with the accepted answer that ef is probably the wrong technology for bulk inserts.
However, I think it's worth having a look at EntityFramework.BulkInsert.

execute stored proc in ExecuteStoreQuery EF. is this a bug in EF?

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

How to pass default keyword for table valued function call in ADO.NET

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