如何在 运行 核心中 运行 存储过程?
How to run stored procedures in Entity Framework Core?
我在 ASP.NET 核心应用程序中使用 EF Core 1.0。你能告诉我执行存储过程的正确方法吗? ObjectParameters
和 ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction
的旧方法不起作用。
存储过程支持尚未在 EF7 中实现(截至 7.0.0-beta3)。您可以使用问题 #245.
跟踪此功能的进度
现在,您可以使用 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();
"(SqlConnection)context"
-- 这种类型转换不再有效。你可以这样做:"SqlConnection context;
".AsSqlServer()"
-- 不存在。
"command.ExecuteNonQuery();"
-- 没有 return 结果。 reader=command.ExecuteReader()
确实有效。
使用 dt.load(reader)... 然后您必须将框架从 5.0 切换回 4.51,因为 5.0 尚不支持 datatables/datasets。注:这是VS2015 RC.
EF Core 1.0 中对存储过程的支持现已解决,这也支持多个结果集的映射。
Check here for the fix details
你可以在c#中这样调用它
var userType = dbContext.Set().FromSql("dbo.SomeSproc @Id = {0}, @Name = {1}", 45, "Ada");
我在ExecuteSqlCommand
和ExecuteSqlCommandAsync
上遇到了很多麻烦,IN参数很容易,但是OUT参数很难。
我不得不像这样恢复使用 DbCommand
-
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 });
我在 this post 中写了更多关于它的内容。
目前EF 7或EF Core不支持在设计器中导入存储过程并直接调用的旧方法。您可以查看路线图以了解未来将支持的内容:
EF core roadmap.
所以现在最好使用 SqlConnection 来调用存储过程或任何原始查询,因为您不需要整个 EF 来完成这项工作。这里有两个例子:
调用return单个值的存储过程。在本例中为字符串。
CREATE PROCEDURE [dbo].[Test]
@UserName nvarchar(50)
AS
BEGIN
SELECT 'Name is: '+@UserName;
END
调用return一个列表的存储过程。
CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END
要调用这些存储过程,最好创建包含所有这些函数的静态 class,例如,我将其称为 DataAccess class,如下所示:
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;
}
}
}
}
而用户 class 是这样的:
public class Users
{
public string UserName { set; get; }
public string Id { set; get; }
}
顺便说一句,您无需担心为 sql 的每个请求打开和关闭连接的性能,因为 asp.net 正在为您管理这些。
我希望这对您有所帮助。
要执行存储过程,请使用执行 RAW SQL 查询的 FromSql 方法
例如
var products= context.Products
.FromSql("EXECUTE dbo.GetProducts")
.ToList();
与参数一起使用
var productCategory= "Electronics";
var product = context.Products
.FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
.ToList();
或
var productCategory= new SqlParameter("productCategory", "Electronics");
var product = context.Product
.FromSql("EXECUTE dbo.GetProductByName @productCategory", productCategory)
.ToList();
执行 RAW SQL 查询或存储过程存在某些限制。您不能将它用于 INSERT/UPDATE/DELETE。如果要执行 INSERT、UPDATE、DELETE 查询,请使用 ExecuteSqlCommand
var categoryName = "Electronics";
dataContext.Database
.ExecuteSqlCommand("dbo.InsertCategory @p0", categoryName);
EF Core 对存储过程的支持与早期版本的 EF Code 相似。
您需要通过从 EF 继承 DbContext class 来创建 DbContext class。存储过程正在使用 DbContext 执行。
第一步是编写一个从 DbContext 创建 DbCommand 的方法。
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;
}
要将参数传递给存储过程,请使用以下方法。
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;
}
最后使用 MapToList 方法将结果映射到自定义对象列表。
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;
}
现在我们准备好使用ExecuteStoredProc方法执行存储过程并将其映射到类型为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();
}
}
}
例如,要执行一个名为“StoredProcedureName”的存储过程,带有两个参数“firstparamname”和“secondparamname”,这就是实现。
List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
myTypeList = context.LoadStoredProc("StoredProcedureName")
.WithSqlParam("firstparamname", firstParamValue)
.WithSqlParam("secondparamname", secondParamValue).
.ExecureStoredProc<MyType>();
}
如果您使用 EntityFrameworkCore 从 Informix 执行存储过程,则需要包含命令 EXECUTE PROCEDURE
var spresult = _informixContext.procdata.FromSql("EXECUTE PROCEDURE dummyproc ({0},{1},{2})", parameters: new[] { p0, p1,p2 }).ToList();
使用 MySQL 连接器和 Entity Framework Core 2.0
我的问题是我遇到了类似 fx 的异常。 Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation."。因此,为了以这种方式通过存储过程获取行,您必须 return 与 DBSet 相关联的实体类型的所有列,即使您不需要为当前访问所有列要求。
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
或带参数
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();
无需执行任何操作...当您为代码优先方法初始化创建 dbcontext 时
fluent API 区域下方的命名空间制作 sp 列表并将其用于您想要的其他地方。
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; }
}
我发现这个扩展非常有用:StoredProcedureEFCore
那么用法就是这样
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);
我尝试了所有其他解决方案,但都不适合我。但我找到了一个合适的解决方案,这可能对这里的人有帮助。
要调用存储过程并将结果放入 EF Core 中的模型列表中,我们必须执行 3 个步骤。
步骤 1.
您需要添加一个新的 class,就像您的实体 class 一样。哪个应该具有 SP 中所有列的属性。例如,如果您的 SP 返回两个名为 Id
和 Name
的列,那么您的新 class 应该类似于
public class MySPModel
{
public int Id {get; set;}
public string Name {get; set;}
}
步骤 2.
然后您必须为您的 SP 添加一个 DbQuery
属性 到您的 DBContext class。
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
...
}
步骤 3.
现在您将能够从您的 DBContext 中调用并从您的 SP 获取结果。
var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();
我正在使用通用的 UnitOfWork 和存储库。所以我执行 SP 的函数是
/// <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();
}
希望对大家有所帮助!!!
我正在使用 Entity Framework Core 和我的 ASP.Net Core 3.x WebAPI。我想要我的终点之一只是执行一个特定的存储过程,这是我需要的代码:
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);
}
}
}
}
请注意我需要如何获取已注入的 DbContext
,但我还需要 Open()
此连接。
我们应该在模型中为数据库上下文创建一个 属性,其中 DbQuery 而不是 DbSet...
public class MyContextContext : DbContext
{
public virtual DbQuery<CheckoutInvoiceModel> CheckoutInvoice { get; set; }
}
比一个方法后可以用来return结果
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;
}
从上面的例子中,我们可以使用你喜欢的任何一个选项。
希望对您有所帮助!
根据存储过程的 Select 查询中的字段创建特殊的 class。
例如,我将调用此 class ResulData
添加到您的上下文中 EF
modelBuilder.Entity<ResultData>(e =>
{
e.HasNoKey();
});
这是一个使用存储过程获取数据的示例函数
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();
}
使用了 StoredProcedureEFCore nuget 包
我尝试使用{存储库模式}的 DbFirst 方法..我认为是
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 接口有 {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)));
}
}
}
}
由于我的团队同意我们将使用通用 UnitOfWork 模式,因此我在创建我的解决方案时采用了一些每个人的解决方案。
我也发布了一些 UnitOfWork 代码,这样您就可以了解为什么我需要这样实现它。
public interface IUnitOfWork : IDisposable
{
DbContext Context { get; }
Task<List<T>> ExecuteStoredProc<T>(string storedProcName, Dictionary<string, object> procParams) where T : class;
}
接口实现:
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
}
示例用法如下:
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;
}
}
我正在使用 Entity Framework Core。对存储过程和即席查询的支持不像在 Framework 中那样流畅。
这里有一些例子供以后参考:
根据存储过程的结果填充实体列表:
[dbo].[GetUnarchivedJobs]
存储过程 returns 匹配 Job
实体的记录列表。
我们可以在 Jobs
属性 上使用 FromSqlInterpolated()
方法来调用存储过程并返回 Job
的列表。
NoTracking()
用于加速性能,在这种情况下我们不会更新实体。
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) { }
}
将整数数组发送到存储过程:
[dbo].[SetJobListArchiveFlags]
存储过程有一个类型为 integer_list_tbltype
的参数。
我们需要创建一个 DataTable
来匹配 integer_list_tbltype
类型,它有一个名为 n
.
的列
需要将 int
值添加到 DataTable
。
A SqlParameter
用于将填充的 DataTable
传递给存储过程。
因为我们没有填充任何实体,所以我们需要使用 Database
属性 上的方法来调用存储过程。
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;
}
}
我在 ASP.NET 核心应用程序中使用 EF Core 1.0。你能告诉我执行存储过程的正确方法吗? ObjectParameters
和 ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction
的旧方法不起作用。
存储过程支持尚未在 EF7 中实现(截至 7.0.0-beta3)。您可以使用问题 #245.
跟踪此功能的进度现在,您可以使用 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();
"(SqlConnection)context"
-- 这种类型转换不再有效。你可以这样做:"SqlConnection context;
".AsSqlServer()"
-- 不存在。
"command.ExecuteNonQuery();"
-- 没有 return 结果。 reader=command.ExecuteReader()
确实有效。
使用 dt.load(reader)... 然后您必须将框架从 5.0 切换回 4.51,因为 5.0 尚不支持 datatables/datasets。注:这是VS2015 RC.
EF Core 1.0 中对存储过程的支持现已解决,这也支持多个结果集的映射。
Check here for the fix details
你可以在c#中这样调用它
var userType = dbContext.Set().FromSql("dbo.SomeSproc @Id = {0}, @Name = {1}", 45, "Ada");
我在ExecuteSqlCommand
和ExecuteSqlCommandAsync
上遇到了很多麻烦,IN参数很容易,但是OUT参数很难。
我不得不像这样恢复使用 DbCommand
-
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 });
我在 this post 中写了更多关于它的内容。
目前EF 7或EF Core不支持在设计器中导入存储过程并直接调用的旧方法。您可以查看路线图以了解未来将支持的内容: EF core roadmap.
所以现在最好使用 SqlConnection 来调用存储过程或任何原始查询,因为您不需要整个 EF 来完成这项工作。这里有两个例子:
调用return单个值的存储过程。在本例中为字符串。
CREATE PROCEDURE [dbo].[Test]
@UserName nvarchar(50)
AS
BEGIN
SELECT 'Name is: '+@UserName;
END
调用return一个列表的存储过程。
CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END
要调用这些存储过程,最好创建包含所有这些函数的静态 class,例如,我将其称为 DataAccess class,如下所示:
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;
}
}
}
}
而用户 class 是这样的:
public class Users
{
public string UserName { set; get; }
public string Id { set; get; }
}
顺便说一句,您无需担心为 sql 的每个请求打开和关闭连接的性能,因为 asp.net 正在为您管理这些。 我希望这对您有所帮助。
要执行存储过程,请使用执行 RAW SQL 查询的 FromSql 方法
例如
var products= context.Products
.FromSql("EXECUTE dbo.GetProducts")
.ToList();
与参数一起使用
var productCategory= "Electronics";
var product = context.Products
.FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
.ToList();
或
var productCategory= new SqlParameter("productCategory", "Electronics");
var product = context.Product
.FromSql("EXECUTE dbo.GetProductByName @productCategory", productCategory)
.ToList();
执行 RAW SQL 查询或存储过程存在某些限制。您不能将它用于 INSERT/UPDATE/DELETE。如果要执行 INSERT、UPDATE、DELETE 查询,请使用 ExecuteSqlCommand
var categoryName = "Electronics";
dataContext.Database
.ExecuteSqlCommand("dbo.InsertCategory @p0", categoryName);
EF Core 对存储过程的支持与早期版本的 EF Code 相似。
您需要通过从 EF 继承 DbContext class 来创建 DbContext class。存储过程正在使用 DbContext 执行。
第一步是编写一个从 DbContext 创建 DbCommand 的方法。
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;
}
要将参数传递给存储过程,请使用以下方法。
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;
}
最后使用 MapToList 方法将结果映射到自定义对象列表。
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;
}
现在我们准备好使用ExecuteStoredProc方法执行存储过程并将其映射到类型为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();
}
}
}
例如,要执行一个名为“StoredProcedureName”的存储过程,带有两个参数“firstparamname”和“secondparamname”,这就是实现。
List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
myTypeList = context.LoadStoredProc("StoredProcedureName")
.WithSqlParam("firstparamname", firstParamValue)
.WithSqlParam("secondparamname", secondParamValue).
.ExecureStoredProc<MyType>();
}
如果您使用 EntityFrameworkCore 从 Informix 执行存储过程,则需要包含命令 EXECUTE PROCEDURE
var spresult = _informixContext.procdata.FromSql("EXECUTE PROCEDURE dummyproc ({0},{1},{2})", parameters: new[] { p0, p1,p2 }).ToList();
使用 MySQL 连接器和 Entity Framework Core 2.0
我的问题是我遇到了类似 fx 的异常。 Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation."。因此,为了以这种方式通过存储过程获取行,您必须 return 与 DBSet 相关联的实体类型的所有列,即使您不需要为当前访问所有列要求。
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
或带参数
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();
无需执行任何操作...当您为代码优先方法初始化创建 dbcontext 时 fluent API 区域下方的命名空间制作 sp 列表并将其用于您想要的其他地方。
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; }
}
我发现这个扩展非常有用:StoredProcedureEFCore
那么用法就是这样
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);
我尝试了所有其他解决方案,但都不适合我。但我找到了一个合适的解决方案,这可能对这里的人有帮助。
要调用存储过程并将结果放入 EF Core 中的模型列表中,我们必须执行 3 个步骤。
步骤 1.
您需要添加一个新的 class,就像您的实体 class 一样。哪个应该具有 SP 中所有列的属性。例如,如果您的 SP 返回两个名为 Id
和 Name
的列,那么您的新 class 应该类似于
public class MySPModel
{
public int Id {get; set;}
public string Name {get; set;}
}
步骤 2.
然后您必须为您的 SP 添加一个 DbQuery
属性 到您的 DBContext class。
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
...
}
步骤 3.
现在您将能够从您的 DBContext 中调用并从您的 SP 获取结果。
var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();
我正在使用通用的 UnitOfWork 和存储库。所以我执行 SP 的函数是
/// <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();
}
希望对大家有所帮助!!!
我正在使用 Entity Framework Core 和我的 ASP.Net Core 3.x WebAPI。我想要我的终点之一只是执行一个特定的存储过程,这是我需要的代码:
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);
}
}
}
}
请注意我需要如何获取已注入的 DbContext
,但我还需要 Open()
此连接。
我们应该在模型中为数据库上下文创建一个 属性,其中 DbQuery 而不是 DbSet...
public class MyContextContext : DbContext
{
public virtual DbQuery<CheckoutInvoiceModel> CheckoutInvoice { get; set; }
}
比一个方法后可以用来return结果
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;
}
从上面的例子中,我们可以使用你喜欢的任何一个选项。
希望对您有所帮助!
根据存储过程的 Select 查询中的字段创建特殊的 class。 例如,我将调用此 class ResulData
添加到您的上下文中 EF
modelBuilder.Entity<ResultData>(e =>
{
e.HasNoKey();
});
这是一个使用存储过程获取数据的示例函数
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();
}
我尝试使用{存储库模式}的 DbFirst 方法..我认为是
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 接口有 {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)));
}
}
}
}
由于我的团队同意我们将使用通用 UnitOfWork 模式,因此我在创建我的解决方案时采用了一些每个人的解决方案。
我也发布了一些 UnitOfWork 代码,这样您就可以了解为什么我需要这样实现它。
public interface IUnitOfWork : IDisposable
{
DbContext Context { get; }
Task<List<T>> ExecuteStoredProc<T>(string storedProcName, Dictionary<string, object> procParams) where T : class;
}
接口实现:
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
}
示例用法如下:
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;
}
}
我正在使用 Entity Framework Core。对存储过程和即席查询的支持不像在 Framework 中那样流畅。
这里有一些例子供以后参考:
根据存储过程的结果填充实体列表:
[dbo].[GetUnarchivedJobs]
存储过程 returns 匹配 Job
实体的记录列表。
我们可以在 Jobs
属性 上使用 FromSqlInterpolated()
方法来调用存储过程并返回 Job
的列表。
NoTracking()
用于加速性能,在这种情况下我们不会更新实体。
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) { }
}
将整数数组发送到存储过程:
[dbo].[SetJobListArchiveFlags]
存储过程有一个类型为 integer_list_tbltype
的参数。
我们需要创建一个 DataTable
来匹配 integer_list_tbltype
类型,它有一个名为 n
.
需要将 int
值添加到 DataTable
。
A SqlParameter
用于将填充的 DataTable
传递给存储过程。
因为我们没有填充任何实体,所以我们需要使用 Database
属性 上的方法来调用存储过程。
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;
}
}