i'm new to ASP.NET MVC
so i've been trying for the last week to create a dropdown list from sql server to view a list of the interns Names
I've tried various & different ways to solve it, i checked most of the stackoverflow solutions related to the same issue, but all is leading to is the same error 'There is no ViewData item of type 'IEnumerable' that has the key 'InternID'.'
So my question is, What am i missing?
this is my main code
Controller
public ActionResult AddTask(Add_TaskModel Add_Task, HttpPostedFileBase postedFile) {
var InternList = entities.InternsTbls.ToList();
ViewBag.internList = new SelectList(InternList, "InternID","Name");
return View();}
View
#Html.DropDownListFor(m => m.InternID, ViewBag.internList as SelectList, "--Select The Intern", new { #class = "form-cotrnol"})
Model
public class Add_TaskModel
{
[Key]
public int TaskID { get; set; }
public string TaskTitle { get; set; }
public string TaskType { get; set; }
public string TaskDes { get; set; }
public string DueDate { get; set; }
public int SupID { get; set; }
public string TaskStatus { get; set; }
public int FileID { get; set; }
public int InternID { get; set; }
public string Name { get; set; }
}
Note- this one of many ways i tried, but same error.
Code 2
//var myList = new List<SelectListItem>();
//var list = entities.InternsTbls.ToList();
//var items = from g in list
// select new SelectListItem
// {
// Value = g.InternID.ToString(),
// Text = g.Name.ToString()
// };
//foreach (var item in items)
// myList.Add(item);
//ViewBag["In_List"] = myList;
Code 3
//string mainconn = ConfigurationManager.ConnectionStrings["TaskTrackerDBEntities"].ConnectionString;
//if (mainconn.ToLower().StartsWith("metadata="))
//{
// System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder efBuilder = new System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder(mainconn);
// mainconn = efBuilder.ProviderConnectionString;
//}
//using (SqlConnection sqlconn = new SqlConnection(mainconn))
//{
// string sqlquery = "select * from [dbo].[InternsTbl]";
// using (SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn))
// {
// sqlconn.Open();
// SqlDataAdapter sda = new SqlDataAdapter(sqlcomm);
// DataSet ds = new DataSet();
// sda.Fill(ds);
// ViewBag.internname = ds.Tables[0];
// List<SelectListItem> getinternname = new List<SelectListItem>();
// foreach (System.Data.DataRow dr in ViewBag.internname.Rows)
// {
// getinternname.Add(new SelectListItem { Text = #dr["Name"].ToString(), Value = #dr["Name"].ToString() });
// }
// ViewData ["Interns"] = getinternname;
// //new SelectList(getinternname, "Name", "Name");
// sqlconn.Close();
// }
//}
Code 4
//TaskTrackerDBEntities entities1 = new TaskTrackerDBEntities();
//var getInternName = entities1.InternsTbls.ToList();
//SelectList list = new SelectList(getInternName, "InternID", "Name");
// ViewBag["InternListName"] = list;
//ViewBag.SubNames = new SelectList(entities1.InternsNames, "InternID", "Name");
I am using EF Core 1.0 in an ASP.NET Core App. Can you please point me to the proper way of executing stored procedures? The old method with ObjectParameters and ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction is not working.
Support for stored procedures in EF Core 1.0 is resolved now, this also supports the mapping of multiple result-sets.
Check here for the fix details
And you can call it like this in c#
var userType = dbContext.Set().FromSql("dbo.SomeSproc #Id = {0}, #Name = {1}", 45, "Ada");
Stored procedure support is not yet (as of 7.0.0-beta3) implemented in EF7. You can track the progress of this feature using issue #245.
For now, you can do it the old fashioned way using ADO.NET.
var connection = (SqlConnection)context.Database.AsSqlServer().Connection.DbConnection;
var command = connection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "MySproc";
command.Parameters.AddWithValue("#MyParameter", 42);
command.ExecuteNonQuery();
To execute the stored procedures, use FromSql method which executes RAW SQL queries
e.g.
var products= context.Products
.FromSql("EXECUTE dbo.GetProducts")
.ToList();
To use with parameters
var productCategory= "Electronics";
var product = context.Products
.FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
.ToList();
or
var productCategory= new SqlParameter("productCategory", "Electronics");
var product = context.Product
.FromSql("EXECUTE dbo.GetProductByName #productCategory", productCategory)
.ToList();
There are certain limitations to execute RAW SQL queries or stored procedures. You can’t use it for INSERT/UPDATE/DELETE. if you want to execute INSERT, UPDATE, DELETE queries, use the ExecuteSqlCommand
var categoryName = "Electronics";
dataContext.Database
.ExecuteSqlCommand("dbo.InsertCategory #p0", categoryName);
The support for stored procedure in EF Core is similar to the earlier versions of EF Code first.
You need to create your DbContext class by inherting the DbContext class from EF. The stored procedures are executing using the DbContext.
First step is to write a method that create a DbCommand from the DbContext.
public static DbCommand LoadStoredProc(
this DbContext context, string storedProcName)
{
var cmd = context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = storedProcName;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
return cmd;
}
To pass parameters to the stored procedure use the following method.
public static DbCommand WithSqlParam(
this DbCommand cmd, string paramName, object paramValue)
{
if (string.IsNullOrEmpty(cmd.CommandText))
throw new InvalidOperationException(
"Call LoadStoredProc before using this method");
var param = cmd.CreateParameter();
param.ParameterName = paramName;
param.Value = paramValue;
cmd.Parameters.Add(param);
return cmd;
}
Finally for mapping the result into a list of custom objects use the MapToList method.
private static List<T> MapToList<T>(this DbDataReader dr)
{
var objList = new List<T>();
var props = typeof(T).GetRuntimeProperties();
var colMapping = dr.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
if (dr.HasRows)
{
while (dr.Read())
{
T obj = Activator.CreateInstance<T>();
foreach (var prop in props)
{
var val =
dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
prop.SetValue(obj, val == DBNull.Value ? null : val);
}
objList.Add(obj);
}
}
return objList;
}
Now we’re ready for execute the stored procedute with the ExecuteStoredProc method and maps it to the a List whose type that’s passed in as T.
public static async Task<List<T>> ExecuteStoredProc<T>(this DbCommand command)
{
using (command)
{
if (command.Connection.State == System.Data.ConnectionState.Closed)
command.Connection.Open();
try
{
using (var reader = await command.ExecuteReaderAsync())
{
return reader.MapToList<T>();
}
}
catch(Exception e)
{
throw (e);
}
finally
{
command.Connection.Close();
}
}
}
For example, to execute a stored procedure called “StoredProcedureName” with two parameters called “firstparamname” and “secondparamname” this is the implementation.
List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
myTypeList = context.LoadStoredProc("StoredProcedureName")
.WithSqlParam("firstparamname", firstParamValue)
.WithSqlParam("secondparamname", secondParamValue).
.ExecureStoredProc<MyType>();
}
I tried all the other solutions but didn't worked for me. But I came to a proper solution and it may be helpful for someone here.
To call a stored procedure and get the result into a list of model in EF Core, we have to follow 3 steps.
Step 1.
You need to add a new class just like your entity class. Which should have properties with all the columns in your SP. For example if your SP is returning two columns called Id and Name then your new class should be something like
public class MySPModel
{
public int Id {get; set;}
public string Name {get; set;}
}
Step 2.
Then you have to add one DbQuery property into your DBContext class for your SP.
public partial class Sonar_Health_AppointmentsContext : DbContext
{
public virtual DbSet<Booking> Booking { get; set; } // your existing DbSets
...
public virtual DbQuery<MySPModel> MySP { get; set; } // your new DbQuery
...
}
Step 3.
Now you will be able to call and get the result from your SP from your DBContext.
var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();
I am using a generic UnitOfWork & Repository. So my function to execute the SP is
/// <summary>
/// Execute function. Be extra care when using this function as there is a risk for SQL injection
/// </summary>
public async Task<IEnumerable<T>> ExecuteFuntion<T>(string functionName, string parameter) where T : class
{
return await _context.Query<T>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();
}
Hope it will be helpful for someone !!!
"(SqlConnection)context"
-- This type-casting no longer works. You can do: "SqlConnection context;
".AsSqlServer()"
-- Does not Exist.
"command.ExecuteNonQuery();"
-- Does not return results. reader=command.ExecuteReader() does work.
With dt.load(reader)... then you have to switch the framework out of 5.0 and back to 4.51, as 5.0 does not support datatables/datasets, yet. Note: This is VS2015 RC.
Currently EF 7 or EF Core does not support the old method of importing Stored procedures in designer and calling them directly. You can have a look at the roadmap to see what is going to be supported in the future:
EF core roadmap.
So for now it is better to use SqlConnection to call stored procedures or any raw query, since you do not need the entire EF for this job. Here are two examples:
Call stored procedure that return single value. String in this case.
CREATE PROCEDURE [dbo].[Test]
#UserName nvarchar(50)
AS
BEGIN
SELECT 'Name is: '+#UserName;
END
Call stored procedure that return a list.
CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END
To call these stored procedure it is better to create static class that holds all of these functions, for example, I called it DataAccess class, as follows:
public static class DataAccess
{
private static string connectionString = ""; //Your connection string
public static string Test(String userName)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
// 1. create a command object identifying the stored procedure
SqlCommand cmd = new SqlCommand("dbo.Test", conn);
// 2. set the command object so it knows to execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;
// 3. add parameter to command, which will be passed to the stored procedure
cmd.Parameters.Add(new SqlParameter("#UserName", userName));
// execute the command
using (var rdr = cmd.ExecuteReader())
{
if (rdr.Read())
{
return rdr[0].ToString();
}
else
{
return null;
}
}
}
}
public static IList<Users> TestList()
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
// 1. create a command object identifying the stored procedure
SqlCommand cmd = new SqlCommand("dbo.TestList", conn);
// 2. set the command object so it knows to execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;
// execute the command
using (var rdr = cmd.ExecuteReader())
{
IList<Users> result = new List<Users>();
//3. Loop through rows
while (rdr.Read())
{
//Get each column
result.Add(new Users() { UserName = (string)rdr.GetString(0), Id = rdr.GetString(1) });
}
return result;
}
}
}
}
And Users class is like this:
public class Users
{
public string UserName { set; get; }
public string Id { set; get; }
}
By the way you do not need to worry about the performance of opening and closing a connection for every request to sql as the asp.net is taking care of managing these for you.
And I hope this was helpful.
I had a lot of trouble with the ExecuteSqlCommand and ExecuteSqlCommandAsync, IN parameters were easy, but OUT parameters were very difficult.
I had to revert to using DbCommand like so -
DbCommand cmd = _context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "dbo.sp_DoSomething";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#firstName", SqlDbType.VarChar) { Value = "Steve" });
cmd.Parameters.Add(new SqlParameter("#lastName", SqlDbType.VarChar) { Value = "Smith" });
cmd.Parameters.Add(new SqlParameter("#id", SqlDbType.BigInt) { Direction = ParameterDirection.Output });
I wrote more about it in this post.
Since it is agreed in my team that we are going to use generic UnitOfWork pattern, I have taken a bit of everyone's solutions when creating mine.
I am posting a bit of UnitOfWork code as well so you can get a grasp of why I needed to implement it like so.
public interface IUnitOfWork : IDisposable
{
DbContext Context { get; }
Task<List<T>> ExecuteStoredProc<T>(string storedProcName, Dictionary<string, object> procParams) where T : class;
}
Interface implementation:
public class UnitOfWork : IUnitOfWork
{
public DbContext Context { get; private set; }
/// <summary>
/// Execute procedure from database using it's name and params that is protected from the SQL injection attacks.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="storedProcName">Name of the procedure that should be executed.</param>
/// <param name="procParams">Dictionary of params that procedure takes. </param>
/// <returns>List of objects that are mapped in T type, returned by procedure.</returns>
public async Task<List<T>> ExecuteStoredProc<T>(string storedProcName, Dictionary<string, object> procParams) where T : class
{
DbConnection conn = Context.Database.GetDbConnection();
try
{
if(conn.State != ConnectionState.Open)
await conn.OpenAsync();
await using (DbCommand command = conn.CreateCommand())
{
command.CommandText = storedProcName;
command.CommandType = CommandType.StoredProcedure;
foreach (KeyValuePair<string, object> procParam in procParams)
{
DbParameter param = command.CreateParameter();
param.ParameterName = procParam.Key;
param.Value = procParam.Value;
command.Parameters.Add(param);
}
DbDataReader reader = await command.ExecuteReaderAsync();
List<T> objList = new List<T>();
IEnumerable<PropertyInfo> props = typeof(T).GetRuntimeProperties();
Dictionary<string, DbColumn> colMapping = reader.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
T obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in props)
{
object val =
reader.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
prop.SetValue(obj, val == DBNull.Value ? null : val);
}
objList.Add(obj);
}
}
reader.Dispose();
return objList;
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message, e.InnerException);
}
finally
{
conn.Close();
}
return null; // default state
}
Example usage is like so:
public class MyService : IMyService
{
private readonly IUnitOfWork _uow;
public MyService(IUnitOfWork uow)
{
_uow = uow;
}
public async Task<List<TreeViewModel>> GetTreeOptions()
{
var procParams = new Dictionary<string, object>()
{
{"#Id", 2}
};
var result = await _uow.ExecuteStoredProc<TreeViewModel>("FetchTreeProcedure", procParams);
return result;
}
}
I found this extention very usefull: StoredProcedureEFCore
Then the usage is like this
List<Model> rows = null;
ctx.LoadStoredProc("dbo.ListAll")
.AddParam("limit", 300L)
.AddParam("limitOut", out IOutParam<long> limitOut)
.Exec(r => rows = r.ToList<Model>());
long limitOutValue = limitOut.Value;
ctx.LoadStoredProc("dbo.ReturnBoolean")
.AddParam("boolean_to_return", true)
.ReturnValue(out IOutParam<bool> retParam)
.ExecNonQuery();
bool b = retParam.Value;
ctx.LoadStoredProc("dbo.ListAll")
.AddParam("limit", 1L)
.ExecScalar(out long l);
Using MySQL connector and Entity Framework Core 2.0
My issue was that I was getting an exception like fx. Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation.". So, in order to fetch rows via a stored procedure in this manner, you must return all columns for that entity type which the DBSet is associated with, even if you don't need to access all of it for your current request.
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
OR with parameters
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();
I'm using Entity Framework Core with my ASP.Net Core 3.x WebAPI. I wanted one of my end points just to execute a particular Stored Procedure, and this is the code I needed:
namespace MikesBank.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ResetController : ControllerBase
{
private readonly MikesBankContext _context;
public ResetController(MikesBankContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult> Get()
{
try
{
using (DbConnection conn = _context.Database.GetDbConnection())
{
if (conn.State != System.Data.ConnectionState.Open)
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "Reset_Data";
await cmd.ExecuteNonQueryAsync();
}
return new OkObjectResult(1);
}
catch (Exception ex)
{
return new BadRequestObjectResult(ex.Message);
}
}
}
}
Notice how I need to get my DbContext which has been injected, but I also need to Open() this connection.
Create the special class according the fields in your Select query of your stored procedure.
For example I will call this class ResulData
Add to context of you EF
modelBuilder.Entity<ResultData>(e =>
{
e.HasNoKey();
});
And this a sample function to get data using the store procedure
public async Task<IEnumerable<ResultData>> GetDetailsData(int id, string name)
{
var pId = new SqlParameter("#Id", id);
var pName = new SqlParameter("#Name", name);
return await _context.Set<ResultData>()
.FromSqlRaw("Execute sp_GetDeailsData #Id #Name", parameters: new[] { pId, pName })
.ToArrayAsync();
}
I used StoredProcedureEFCore nuget package by https://github.com/verdie-g/StoredProcedureEFCore,EnterpriseLibrary.Data.NetCore,EFCor.SqlServer,EFCore.Tools
I tried DbFirst approach with {Repository pattern}.. i think so
startup.cs
ConfigureServices(IServiceCollection services){
services.AddDbContext<AppDbContext>(opt => opt
.UseSqlServer(Configuration.GetConnectionString("SampleConnectionString")));
services.AddScoped<ISomeDAL, SomeDAL>();
}
public class AppDbContext : DbContext{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{}
}
ISomeDAl Interface has {GetPropertiesResponse GetAllPropertiesByCity(int CityId);}
public class SomeDAL : ISomeDAL
{
private readonly AppDbContext context;
public SomeDAL(AppDbContext context)
{
this.context = context;
}
public GetPropertiesResponse GetAllPropertiesByCity(int CityId)
{
//Create Required Objects for response
//wont support ref Objects through params
context.LoadStoredProc(SQL_STATEMENT)
.AddParam("CityID", CityId).Exec( r =>
{
while (r.Read())
{
ORMapping<GenericRespStatus> orm = new ORMapping<GenericRespStatus>();
orm.AssignObject(r, _Status);
}
if (r.NextResult())
{
while (r.Read())
{
Property = new Property();
ORMapping<Property> orm = new ORMapping<Property>();
orm.AssignObject(r, Property);
_propertyDetailsResult.Add(Property);
}
}
});
return new GetPropertiesResponse{Status=_Status,PropertyDetails=_propertyDetailsResult};
}
}
public class GetPropertiesResponse
{
public GenericRespStatus Status;
public List<Property> PropertyDetails;
public GetPropertiesResponse()
{
PropertyDetails = new List<Property>();
}
}
public class GenericRespStatus
{
public int ResCode { get; set; }
public string ResMsg { get; set; }
}
internal class ORMapping<T>
{
public void AssignObject(IDataReader record, T myClass)
{
PropertyInfo[] propertyInfos = typeof(T).GetProperties();
for (int i = 0; i < record.FieldCount; i++)
{
if (propertyInfos.Any(obj => obj.Name == record.GetName(i))) //&& record.GetValue(i) != DBNull.Value
{
propertyInfos.Single(obj => obj.Name == record.GetName(i)).SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)));
}
}
}
}
If you are executing a stored procedure from Informix using EntityFrameworkCore you need to include the command EXECUTE PROCEDURE
var spresult = _informixContext.procdata.FromSql("EXECUTE PROCEDURE dummyproc ({0},{1},{2})", parameters: new[] { p0, p1,p2 }).ToList();
Nothing have to do... when you are creating dbcontext for code first approach initialize
namespace below the fluent API area make list of sp and use it another place where you want.
public partial class JobScheduleSmsEntities : DbContext
{
public JobScheduleSmsEntities()
: base("name=JobScheduleSmsEntities")
{
Database.SetInitializer<JobScheduleSmsEntities>(new CreateDatabaseIfNotExists<JobScheduleSmsEntities>());
}
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<ReachargeDetail> ReachargeDetails { get; set; }
public virtual DbSet<RoleMaster> RoleMasters { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Types().Configure(t => t.MapToStoredProcedures());
//modelBuilder.Entity<RoleMaster>()
// .HasMany(e => e.Customers)
// .WithRequired(e => e.RoleMaster)
// .HasForeignKey(e => e.RoleID)
// .WillCascadeOnDelete(false);
}
public virtual List<Sp_CustomerDetails02> Sp_CustomerDetails()
{
//return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Sp_CustomerDetails02>("Sp_CustomerDetails");
// this.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails");
using (JobScheduleSmsEntities db = new JobScheduleSmsEntities())
{
return db.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails").ToList();
}
}
}
}
public partial class Sp_CustomerDetails02
{
public long? ID { get; set; }
public string Name { get; set; }
public string CustomerID { get; set; }
public long? CustID { get; set; }
public long? Customer_ID { get; set; }
public decimal? Amount { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int? CountDay { get; set; }
public int? EndDateCountDay { get; set; }
public DateTime? RenewDate { get; set; }
public bool? IsSMS { get; set; }
public bool? IsActive { get; set; }
public string Contact { get; set; }
}
I'm using Entity Framework Core. The support for stored procedures and adhoc queries doesn't feel as fluid as it does in Framework.
Here are some examples for future reference:
Populate a list of entities from the results of a stored procedure:
The [dbo].[GetUnarchivedJobs] stored procedure returns a list of records that matches the Job entity.
We can use the FromSqlInterpolated() method on the Jobs property to call the stored procedure and have a list of Job returned.
NoTracking() is used to speed up the performance, and in this case we wont be updating the entities.
using Microsoft.EntityFrameworkCore;
public class DbContext : Microsoft.EntityFrameworkCore.DbContext
{
protected DbSet<Job> Jobs { get; set; }
// Populate a list of entities from the results of a stored procedure
public Task<List<Job>> GetUnarchivedJobs(int maxQty, CancellationToken cancellationToken) =>
Jobs.FromSqlInterpolated($"EXEC [dbo].[GetUnarchivedJobs] #MaxQty = {maxQty}")
.AsNoTracking()
.ToListAsync(cancellationToken)
;
public DbContext(DbContextOptions<DbContext> options) : base(options) { }
}
Send an array of integers to a stored procedure:
The [dbo].[SetJobListArchiveFlags] stored procedure has a single parameter of type integer_list_tbltype.
We need to create a DataTable to match the integer_list_tbltype type which has a single column named n.
The int values need to be added to the DataTable.
A SqlParameter is used to pass the populated DataTable to the stored procedure.
Because we are not populating any entities, we need to use methods on the Database property to call the stored procedure.
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
public class DbContext : Microsoft.EntityFrameworkCore.DbContext
{
// Send an array of integers to a stored procedure
public async Task<int> MarkJobsAsArchived(IEnumerable<int> jobIds, CancellationToken cancellationToken)
{
// This DataTable matches the `integer_list_tbltype` db type
var table = new DataTable();
table.Columns.Add("n", typeof(int));
foreach (var id in jobIds) table.Rows.Add(id);
var parameter = new SqlParameter("#jobIds", SqlDbType.Structured);
parameter.Value = table;
parameter.TypeName = "integer_list_tbltype";
var rowsUpdatedCount = await Database.ExecuteSqlRawAsync("EXEC [dbo].[SetJobListArchiveFlags] #jobIds", new[] { parameter }, cancellationToken);
return rowsUpdatedCount;
}
}
From EF core 3.0 onwards, we can also use the following commands:
dbContext.Database.ExecuteSqlInterpolated():
var name = "Ramin";
var outArticleCount = new SqlParameter(
"#outArticleCount",
SqlDbType.Int) { Direction = ParameterDirection.Output };
dbContext.Database.ExecuteSqlInterpolated(
$"exec dbo.MySp {name}, {outputArticleCount} out");
dbContext.Database.ExecuteSqlInterpolatedAsync():
dbContext.Database.ExecuteSqlRaw():
SqlParameter[] parameters =
{
new SqlParameter("#name", SqlDbType.Varchar { Direction = ParameterDirection.Input, Value = "Ramin" },
new SqlParameter("#outArticleCount", SqlDbType.Int { Direction = ParameterDirection.Output }
}
dbContext.Database.ExecuteSqlRaw("exec dbo.MySp #name, #outArticleCount OUTPUT", parameters);
dbContext.Database.ExecuteSqlRawAsync()
Raw and Interpolated versions of above methods are equal but Interpolated versions are safe against SQL Injection attacks (read more here and here).
It's embarrassing how lacking the docs are on this. 😩
It took me a lot of hit and trial to figure this out 😪, so I wanted to document 📝 it here so someone else doesn't have to spend time figuring this out. I'm using Entity Framework Core Version 6.0.10.
Summary
There are 2 kinds of Stored Procedures that return result. And the approach to call them and get result is different from each other.
SPs that return table-based output.
SPs that return output in the output parameters.
SPs that return table based output
If your SP looks like this:
CREATE PROCEDURE dbo.MyTestSPToGetShopType #ShopId AS VARCHAR(25)
AS
SELECT ShopId, ShopType
FROM Shop
WHERE SHOPID = #ShopId
👇
Step 1:
Create a class based on your output
public class MyTestSpResult
{
public string ShopId { get; set; }
public string ShopType { get; set; }
}
Step 2:
Register this in your DbContext's protected override void OnModelCreating(ModelBuilder builder) method:
builder.Entity<MyTestSpResult>(e => e.HasNoKey());
Step 3:
Call the SP from the code, and get the result:
var myTestSpResult = await _dbContext.Set<MyTestSpResult>()
.FromSqlInterpolated($"EXEC dbo.MyTestSPToGetShopType #ShopId = \"A03\"")
.ToArrayAsync();
var myShopType = myTestSpResult[0].ShopType;
Alternate Way
Steps 1 and 2 are same as above.
Step 3:
Register the class from Step 1 in your DbContext:
public virtual DbSet<MyTestSpResult> MyTestSpResult { get; set; }
Step 4:
Call the SP from the code, and get the result:
var mySPResult = await _dbContext.MyTestSpResult
.FromSqlInterpolated($"EXEC dbo.MyTestSPToGetShopType #ShopId = \"A03\"")
.ToArrayAsync();
var myShopType = mySPResult[0].ShopType;
SPs that return output in the output parameters
If your SP looks like this:
CREATE PROCEDURE dbo.MyOtherTestSPToGetShopType
(
#ShopId VARCHAR(25)
,#ShopType VARCHAR(25) OUTPUT
)
AS
SELECT #ShopType = ShopType
FROM Shop
WHERE SHOPID = #ShopId
👇
Step 1:
Create the SQL parameters:
var shopId = new SqlParameter()
{
ParameterName = "#ShopId",
Value = "A03",
Direction = System.Data.ParameterDirection.Input,
SqlDbType = System.Data.SqlDbType.VarChar,
Size = 25
};
var shopType = new SqlParameter()
{
ParameterName = "#ShopType",
Direction = System.Data.ParameterDirection.Output,
SqlDbType = System.Data.SqlDbType.VarChar,
Size = 25
};
Step 2:
Call the SP from code using those parameters, and get the result:
await _dbContext.Database
.ExecuteSqlInterpolatedAsync($"EXEC dbo.MyOtherTestSPToGetShopType #ShopId = {shopId}, #ShopType = {shopType} OUT");
var myShopType = shopType.Value as string;
We should create a property with DbQuery not DbSet in the model for the db context like below...
public class MyContextContext : DbContext
{
public virtual DbQuery<CheckoutInvoiceModel> CheckoutInvoice { get; set; }
}
After than a method that can be used to return result
public async Task<IEnumerable<CheckoutInvoiceModel>> GetLabReceiptByReceiptNo(string labReceiptNo)
{
var listing = new List<CheckoutInvoiceModel>();
try
{
var sqlCommand = $#"[dbo].[Checkout_GetLabReceiptByReceiptNo] {labReceiptNo}";
listing = await db.Set<CheckoutInvoiceModel>().FromSqlRaw(sqlCommand).ToListAsync();
}
catch (Exception ex)
{
return null;
}
return listing;
}
From above example, we can use any one option you like.
Hope this helpful for you!
I have the following model:
public class Model {
public string Name { get; set; }
public List<int> Numbers { get; set; }
}
And an SQL query that returns the following dataset containing two nvarchar columns:
Name
Numbers
foo
1,2,3,4
bar
4,17
Is there a simple way to auto-assign the results of the query to a List<Model> using Dapper?
I know I could use multi-mapping and make the splitting myself in C# code, but I would rather get a simpler solution.
I'm not sure if you can call this "simpler", but something like this is an option:
public class Result
{
public string Name { get; set; }
public List<int> Numbers { get; set; }
}
public class DapperTests
{
[Test]
public void Test()
{
var conn = new SqlConnection(#"Data Source=.\sqlexpress; Integrated Security=true; Initial Catalog=mydb");
conn.Open();
var result = conn.Query<string, string, Result>(
"select Name = 'Foo', Numbers = '1,2,3' union all select Name = 'Bar', Numbers = '4,5,6'", (a, b) => new Result
{
Name = a,
Numbers = b.Split(',').Select(Int32.Parse).ToList()
}, splitOn: "*").ToList();
Assert.That(result.Count, Is.EqualTo(2));
Assert.That(result.FirstOrDefault(x => x.Name == "Foo").Numbers.Count, Is.GreaterThan(0));
Assert.That(result.FirstOrDefault(x => x.Name == "Bar").Numbers.Count, Is.GreaterThan(0));
}
}
An alternative option with multimapping... pretty ugly
public class Result
{
public string Name { get; set; }
public List<int> NumberList { get; set; }
public string Numbers { set { NumberList = value.Split(',').Select(Int32.Parse).ToList(); } }
}
public class DapperTests
{
[Test]
public void Test()
{
var conn = new SqlConnection(#"Data Source=.\sqlexpress; Integrated Security=true; Initial Catalog=mydb");
conn.Open();
var sql = #"
select Name = 'Foo', Numbers = '1,2,3';
select Name = 'Bar', Numbers = '4,5,6';";
var expectedResults = 2;
var results = new List<Result>();
using (var multi = conn.QueryMultiple(sql))
{
for (int i = 0; i < expectedResults; i++)
{
results.Add(multi.Read<Result>().Single());
}
}
Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.FirstOrDefault(x => x.Name == "Foo").NumberList.Count, Is.GreaterThan(0));
Assert.That(results.FirstOrDefault(x => x.Name == "Bar").NumberList.Count, Is.GreaterThan(0));
}
}