我在一个ASP.NET Core应用中使用EF Core 1.0。请问您能否指导我执行存储过程的正确方法?旧的方法使用ObjectParameters
和((IObjectContextAdapter)this).ObjectContext.ExecuteFunction
已经不起作用了。
我在一个ASP.NET Core应用中使用EF Core 1.0。请问您能否指导我执行存储过程的正确方法?旧的方法使用ObjectParameters
和((IObjectContextAdapter)this).ObjectContext.ExecuteFunction
已经不起作用了。
现在,EF Core 1.0已经支持存储过程,并且支持多个结果集的映射。
您可以在C#中如下调用它:
var userType = dbContext.Set().FromSql("dbo.SomeSproc @Id = {0}, @Name = {1}", 45, "Ada");
Set()
。只会得到The arguments for DbContext.Set<TEntity>() cannot be inferred from the usage
。 - Douglas Gaskell要执行存储过程,请使用FromSql方法,该方法可以执行原始的SQL查询。
例如:
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);
ExecuteSqlCommand
зҺ°е·ІиҝҮж—¶гҖӮиҜ·еҸӮйҳ…ExecuteSqlCommandж–№жі•гҖӮ
еҸҜиғҪзҡ„жӣҝд»Јж–№жі•жҳҜExecuteSqlRaw
е’ҢExecuteSqlInterpolated
гҖӮ - Pablo Alexis Domínguez Grau截至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();
context.Database.AsRelational().Connection.DbConnection
。 - bricelamFromSql
。 - bricelamEF Core对存储过程的支持与之前版本的EF Code First类似。
您需要通过继承来自EF的DbContext类来创建您的DbContext类。使用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;
}
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传递的List类型。
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();
}
}
}
List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
myTypeList = context.LoadStoredProc("StoredProcedureName")
.WithSqlParam("firstparamname", firstParamValue)
.WithSqlParam("secondparamname", secondParamValue).
.ExecureStoredProc<MyType>();
}
我尝试了所有其他解决方案,但对我都没有用。但我找到了一个正确的解决方案,它可能对这里的某个人有所帮助。
要调用存储过程并将结果获取到EF Core模型列表中,我们需要遵循3个步骤。
第一步。
您需要添加一个新类,就像实体类一样。该类应具有与SP中所有列相对应的属性。例如,如果您的SP返回两列名为Id
和Name
,则您的新类应该是:
public class MySPModel
{
public int Id {get; set;}
public string Name {get; set;}
}
步骤2。
然后,您需要将一个DbQuery
属性添加到您的DBContext类中,用于您的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
...
}
第三步。
现在,您将能够从您的DBContext中调用并获取来自SP的结果。
var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();
我正在使用通用的UnitOfWork和Repository。因此,我的执行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();
}
"(SqlConnection)context"
-- 这种强制类型转换不再有效,你可以这样写:"SqlConnection context;
".AsSqlServer()"
-- 不存在。
"command.ExecuteNonQuery();"
-- 不返回结果。要返回结果,你需要使用reader=command.ExecuteReader()
。
如果你用dt.load(reader)
,那么你需要将框架从5.0切换回4.51,因为5.0还不支持数据表/数据集。注意:这是VS2015 RC版本。
Entity Framework Core Version 6.0.10
。有两种返回结果的存储过程。调用它们并获取结果的方法各不相同:
如果您的存储过程看起来像这样:
CREATE PROCEDURE dbo.MyTestSPToGetShopType @ShopId AS VARCHAR(25)
AS
SELECT ShopId, ShopType
FROM Shop
WHERE SHOPID = @ShopId
步骤1:基于您的输出创建一个类。public class MyTestSpResult
{
public string ShopId { get; set; }
public string ShopType { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
方法中注册此内容:builder.Entity<MyTestSpResult>(e => e.HasNoKey());
var myTestSpResult = await _dbContext.Set<MyTestSpResult>()
.FromSqlInterpolated($"EXEC dbo.MyTestSPToGetShopType @ShopId = \"A03\"")
.ToArrayAsync();
var myShopType = myTestSpResult[0].ShopType;
步骤1和2与上述相同。
第3步: 在您的DbContext中注册步骤1中的类:
public virtual DbSet<MyTestSpResult> MyTestSpResult { get; set; }
var mySPResult = await _dbContext.MyTestSpResult
.FromSqlInterpolated($"EXEC dbo.MyTestSPToGetShopType @ShopId = \"A03\"")
.ToArrayAsync();
var myShopType = mySPResult[0].ShopType;
如果您的存储过程看起来像这样:
CREATE PROCEDURE dbo.MyOtherTestSPToGetShopType
(
@ShopId VARCHAR(25)
,@ShopType VARCHAR(25) OUTPUT
)
AS
SELECT @ShopType = ShopType
FROM Shop
WHERE SHOPID = @ShopId
步骤1:创建SQL参数: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
};
await _dbContext.Database
.ExecuteSqlInterpolatedAsync($"EXEC dbo.MyOtherTestSPToGetShopType @ShopId = {shopId}, @ShopType = {shopType} OUT");
var myShopType = shopType.Value as string;
MyTestSpResult
模型内,创建 List 属性来建模你的集合。然后只需调用 SP;myTestSpResult[0]
将有第一个集合,myTestSpResult[1]
将有第二个集合,以此类推。 - Ash KCREATE PROCEDURE [dbo].[Test]
@UserName nvarchar(50)
AS
BEGIN
SELECT 'Name is: '+@UserName;
END
CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END
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;
}
}
}
}
用户类(Users class)如下:
public class Users
{
public string UserName { set; get; }
public string Id { set; get; }
}
顺便提一句,您不需要担心每个请求到sql的打开和关闭连接的性能,因为asp.net已经为您管理了这些。
希望对您有所帮助。
我在使用 ExecuteSqlCommand
和 ExecuteSqlCommandAsync
时遇到了很多麻烦,输入参数是容易处理的,但输出参数非常困难。
我不得不回归使用 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 });
我在这篇文章中更详细地介绍了它。
由于我们团队已经决定使用通用的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;
}
}
DbSet
结果吗?还是只需运行它? - haim770