实体框架核心存储过程

时间:2016-05-21 19:44:13

标签: stored-procedures entity-framework-core

我正在使用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

但不是如果我的返回值不是上下文中的实体。

任何帮助都将不胜感激。

4 个答案:

答案 0 :(得分:6)

这很容易。

这是执行此操作的3个步骤:

  1. 创建具有SP返回的相同属性的模型类,例如
  2. 
        public class SPModel
        {
          public int Id {get;set;}
          public DateTime? CreatedDateTime {get;set;}
          etc..
        }
    
    
    1. 将此模型类插入dbContext类,如:
    2. 
          public class YourDbContext: DbContext
          {
             public virtual DbSet<SPModel> YourDbSet { get; set; }
             ....
          }
      
      

      不要将此类映射到任何sql表

      1. 使用它来调用SP,如:
      2.  
            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返回两个列,分别为IdName,则您的新类应为

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