web-dev-qa-db-fra.com

Comment exécuter des procédures stockées dans Entity Framework Core?

J'utilise EF7 (core framework d'entité) dans une application asp.net core. Pouvez-vous m'indiquer la bonne façon d'exécuter des procédures stockées? L'ancienne méthode avec ObjectParameters et ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction ne fonctionne pas.

46
eadam

La prise en charge des procédures stockées dans EF7 est maintenant résolue. Elle prend également en charge le mappage de plusieurs ensembles de résultats.

Vérifiez ici les détails du correctif

Et vous pouvez l'appeler comme ceci - var userType = dbContext.Set().FromSql("dbo.SomeSproc @Id = {0}, @Name = {1}", 45, "Ada");

60
Arvin

La prise en charge des procédures stockées n’est pas encore mise à jour (à partir de la version 7.0.0-beta3) dans EF7. Vous pouvez suivre l'évolution de cette fonctionnalité à l'aide du problème # 245 .

Pour l'instant, vous pouvez le faire à l'ancienne en utilisant 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();
19
bricelam

Pour exécuter les procédures stockées, utilisez la méthode FromSql qui exécute les requêtes SQL RAW

par exemple.

var products= context.Products
    .FromSql("EXECUTE dbo.GetProducts")
    .ToList();

A utiliser avec des paramètres

var productCategory= "Electronics";

var product = context.Products
    .FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
    .ToList();

ou

var productCategory= new SqlParameter("productCategory", "Electronics");

var product = context.Product
    .FromSql("EXECUTE dbo.GetProductByName  @productCategory", productCategory)
    .ToList();

Il existe certaines limitations pour exécuter des requêtes SQL RAW ou des procédures stockées. Vous ne pouvez pas l’utiliser pour INSERT/UPDATE/DELETE. si vous voulez exécuter des requêtes INSERT, UPDATE, DELETE, utilisez la commande ExecuteSqlCommand

var categoryName = "Electronics";
dataContext.Database
           .ExecuteSqlCommand("dbo.InsertCategory @p0", categoryName);
15
Rohith

"(SqlConnection)context" - Cette conversion de type ne fonctionne plus. Vous pouvez faire: "SqlConnection context;

".AsSqlServer()" - N'existe pas.

"command.ExecuteNonQuery();" - Ne renvoie pas de résultats. reader=command.ExecuteReader() fonctionne.

Avec dt.load (lecteur) ..., vous devez alors basculer la structure hors de la version 5.0 et revenir à la version 4.51, car la version 5.0 ne prend pas encore en charge les tables de données/ensembles de données. Remarque: Ceci est VS2015 RC.

4
Rich

La prise en charge des procédures stockées dans EF Core est similaire à celle des versions antérieures de EF Code.

Vous devez créer votre classe DbContext en héritant de la classe DbContext de EF. Les procédures stockées s'exécutent à l'aide de DbContext.

La première étape consiste à écrire une méthode qui crée un DbCommand à partir du 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;
}

Pour transmettre des paramètres à la procédure stockée, appliquez la méthode suivante.

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

Enfin, pour mapper le résultat dans une liste d'objets personnalisés, utilisez la méthode 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;
}

Nous sommes maintenant prêts à exécuter la procédure stockée avec la méthode ExecuteStoredProc et à la mapper à la liste dont le type est transmis en tant que 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();
    }
  }
}

Par exemple, pour exécuter une procédure stockée appelée "StoredProcedureName" avec deux paramètres appelés "firstparamname" et "secondparamname", il s'agit de l'implémentation.

List<MyType> myTypeList = new List<MyType>();
using(var context = new MyDbContext())
{
  myTypeList = context.LoadStoredProc("StoredProcedureName")
  .WithSqlParam("firstparamname", firstParamValue)
  .WithSqlParam("secondparamname", secondParamValue).
  .ExecureStoredProc<MyType>();
}
3
Marco Barbero

Actuellement, EF 7 ou EF Core ne prend pas en charge l'ancienne méthode d'importation des procédures stockées dans Designer et de leur appel direct. Vous pouvez consulter la feuille de route pour voir ce qui sera pris en charge à l'avenir: feuille de route de base EF .

Donc, pour le moment, il est préférable d'utiliser SqlConnection pour appeler des procédures stockées ou toute requête brute, car vous n'avez pas besoin de l'intégralité du EF pour ce travail. Voici deux exemples:

Appelez une procédure stockée qui renvoie une valeur unique. String dans ce cas.

CREATE PROCEDURE [dbo].[Test]
    @UserName nvarchar(50)
AS
BEGIN
    SELECT 'Name is: '+@UserName;
END

Appelez une procédure stockée qui renvoie une liste.

CREATE PROCEDURE [dbo].[TestList]
AS
BEGIN
    SELECT [UserName], [Id] FROM [dbo].[AspNetUsers]
END

Pour appeler ces procédures stockées, il est préférable de créer une classe statique contenant toutes ces fonctions. Je l'ai par exemple appelée la classe DataAccess, comme suit:

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

        }
    }

Et la classe Utilisateurs est comme ça:

public class Users
{
     public string UserName { set; get; }
     public string Id { set; get; }
}

En passant, vous n'avez pas à vous soucier des performances d'ouverture et de fermeture d'une connexion pour chaque requête adressée à SQL, car asp.net se charge de les gérer pour vous. Et j'espère que cela a été utile.

3
Said Al Souti

J'ai essayé toutes les autres solutions mais cela n'a pas fonctionné pour moi. Mais je suis venu à une solution appropriée et cela peut être utile pour quelqu'un ici.

Pour appeler une procédure stockée et obtenir le résultat dans une liste de modèles dans EF Core, vous devez suivre 3 étapes.

Étape 1. Vous devez ajouter une nouvelle classe, tout comme votre classe d'entité. Qui devrait avoir des propriétés avec toutes les colonnes de votre SP. Par exemple, si votre SP renvoie deux colonnes appelées Id et Name, votre nouvelle classe devrait ressembler à

public class MySPModel
{
    public int Id {get; set;}
    public string Name {get; set;}
}

Étape 2.

Ensuite, vous devez ajouter une propriété DbQuery dans votre classe DBContext pour votre 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
        ...
}

étape 3.

Vous pourrez maintenant appeler et obtenir le résultat de votre SP de votre DBContext.

var result = await _context.Query<MySPModel>().AsNoTracking().FromSql(string.Format("EXEC {0} {1}", functionName, parameter)).ToListAsync();

J'utilise un UnitOfWork & Repository générique. Donc, ma fonction pour exécuter le SP est

/// <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();
}

J'espère que ce sera utile pour quelqu'un !!!

1
Abhilash Augustine

J'ai eu beaucoup de problèmes avec les paramètres ExecuteSqlCommand et ExecuteSqlCommandAsync, IN étaient faciles, mais les paramètres OUT étaient très difficiles.

J'ai dû utiliser DbCommand comme si -

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

J'ai écrit plus à ce sujet dans ce post .

1
Bryan

J'ai trouvé cette extension très utile: StoredProcedure EFCore

Alors l'utilisation est comme ça

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);
0
Manfred Wippel

Rien à faire ... lorsque vous créez dbcontext pour le code, commencez par initialiser l'espace de nom sous la zone de l'API fluide, faites la liste de sp et utilisez-le à un autre endroit où vous le souhaitez.

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; }
}
0
SHUBHASIS

Utilisation du connecteur MySQL et d'Entity Framework Core 2.0

Mon problème était que je devenais une exception comme FX. Ex.Message = "La colonne requise 'corps' n'était pas présente dans les résultats d'une opération 'FromSql'.". Ainsi, pour extraire les lignes via une procédure stockée de cette manière, vous devez renvoyer toutes les colonnes de ce type d'entité auquel le DBSet est associé, même si vous n'avez pas besoin de toutes les accéder pour votre demande en cours.

var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList(); 

OU avec paramètres

var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList(); 
0
chri3g91