我正在使用EF Core(代码优先),并希望从存储过程中获取数据。返回的结果应该是我根据结果返回定义的类。
如果返回类型是实体之一,我可以使它工作。恩。
willbuy <- m[!is.na(m)]
mtcars[willbuy, ]
# mpg cyl disp hp drat wt qsec vs am gear carb
# 1: 16.4 8 275.8 180 3.07 4.07 17.40 0 0 3 3
# 2: 21.0 6 160.0 110 3.90 2.62 16.46 0 1 4 4
但不是如果我的返回值不是上下文中的实体。
任何帮助都将不胜感激。
答案 0 :(得分:6)
这很容易。
这是执行此操作的3个步骤:
public class SPModel { public int Id {get;set;} public DateTime? CreatedDateTime {get;set;} etc.. }
public class YourDbContext: DbContext { public virtual DbSet<SPModel> YourDbSet { get; set; } .... }
不要将此类映射到任何sql表
var collection = await dbContext.YourDbSet.FromSql("EXECUTE yourStoredProcedure {0},{1}", param1, param2).ToListAsync();
一些有用的东西:
- 您可以使用try / catch块查看是否遗漏了某些属性
- 您可以扩展模型类以添加新属性,但避免使用“set”访问器:public bool IsLoaded {get;}
- 如果你的SP返回一些可以为空的类型,请小心,在这种情况下,模型也必须具有可空类型
答案 1 :(得分:1)
实体框架Net Core 2.0:执行存储过程并将结果映射到自定义对象列表
EF Core中对存储过程的支持与EF Code的早期版本类似。
您需要通过从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;
}
最后,为了将结果映射到自定义对象列表,请使用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>();
}
我希望这就是你所需要的。
答案 2 :(得分:0)
我尝试了所有其他解决方案,但没有为我工作。但是我找到了一个适当的解决方案,它可能对这里的人有所帮助。
我的原始答案-https://stackoverflow.com/a/57224037/1979465
要调用存储过程并将结果放入EF Core中的模型列表中,我们必须执行3个步骤。
第1步。
您需要像实体类一样添加一个新类。其中应具有SP中所有列的属性。例如,如果您的SP返回两个列,分别为Id
和Name
,则您的新类应为
public class MySPModel
{
public int Id {get; set;}
public string Name {get; set;}
}
第2步。
然后,您必须在SP的DBContext类中添加一个DbQuery
属性。
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();
}
希望对某人有帮助!!!
答案 3 :(得分:0)
这可以在不定义任何DbQuery或DbSet的情况下实现,但需要以下扩展的帮助。 Efcore 3及更高版本
public class CustomType
{
public int Id { get; set; }
public string Name { get; set; }
}
public static class DbContextExtensions
{
public static IList<T> SqlQuery<T>(this DbContext context, string sql, params object[] parameters) where T : class
{
using (var dbcontext = new ContextForQueryType<T>(context.Database.GetDbConnection()))
{
return dbcontext.Set<T>().FromSqlRaw(sql, parameters).AsNoTracking().ToList();
}
}
public static async Task<IList<T>> SqlQueryAsync<T>(this DbContext context, string sql, params object[] parameters) where T : class
{
using (var dbcontext = new ContextForQueryType<T>(context.Database.GetDbConnection()))
{
return await dbcontext.Set<T>().FromSqlRaw(sql, parameters).AsNoTracking().ToListAsync();
}
}
private class ContextForQueryType<T> : DbContext where T : class
{
private readonly System.Data.Common.DbConnection connection;
public ContextForQueryType(System.Data.Common.DbConnection connection)
{
this.connection = connection;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connection, options => options.EnableRetryOnFailure());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<T>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
}
}
并像这样执行:
var param = new SqlParameter("@IdParam", SqlDbType.VarChar, 10);
param.Value = Id.ToString();
string sqlQuery = "Exec [dbo].[usp_get_custom_type] @IdParam";
await context.SqlQueryAsync<CustomType>(sqlQuery);