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.
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");
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();
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);
"(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.
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>();
}
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.
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 !!!
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 .
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);
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; }
}
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();