Depuis quelques jours, je cherche des tutoriels sur la procédure à suivre pour appeler un Stored Procedure
depuis une méthode de contrôleur Web API
à l'aide de EntityFramework 7
.
Tous les tutoriels que j'ai suivis montrent le contraire, c'est-à-dire l'approche Code First
. Mais j'ai déjà une base de données en place et je dois l'utiliser pour créer un Web API
. Diverses logiques d’entreprise sont déjà écrites sous forme de procédures stockées et de vues et je dois les utiliser depuis mon API Web.
Question 1: Est-il possible de continuer avec l'approche Database First
avec EF7
et de consommer des objets de base de données comme ci-dessus?
J'ai installé EntityFramework 6.1.3
dans mon paquet à l'aide de la commande NuGet
suivante:
install-package EntityFramework
qui ajoute la version 6.1.3 à mon projet mais commence immédiatement à afficher un message d'erreur (voir la capture d'écran ci-dessous). Je ne sais pas comment résoudre ce problème.
J'ai un autre projet de test où dans project.json
je peux voir deux entrées comme suit:
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", "EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",
Cependant, lorsque je cherche dans le gestionnaire de paquets Nu-Get
, je ne vois pas cette version! Seul 6.1.3 est à venir.
Mon objectif principal est de consommer des procédures stockées et des vues déjà écrites à partir d'une base de données existante.
1) Je ne veux pas utiliser ADO.Net
, mais plutôt ORM
en utilisant EntityFramework
2) Si EntityFramework 6.1.3
peut appeler Stored Procs
et Views
à partir d'une base de données existante, comment puis-je résoudre l'erreur (capture d'écran)?
Quelle est la meilleure pratique pour y parvenir?
J'espère que j'ai bien compris votre problème. Vous avez une procédure stockée existante, par exemple dbo.spGetSomeData
, dans la base de données, qui renvoie la liste de certains éléments avec certains champs et vous devez fournir les données de la méthode API Web.
La mise en œuvre pourrait concerner les éléments suivants. Vous pouvez définir un vide DbContext
comme:
public class MyDbContext : DbContext
{
}
et définir appsettings.json
avec la chaîne de connexion à la base de données
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
Vous devez utiliser Microsoft.Extensions.DependencyInjection
pour ajouter MyDbContext
au
public class Startup
{
// property for holding configuration
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
.AddEnvironmentVariables();
// save the configuration in Configuration property
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(Configuration["ConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
}
}
Vous pouvez maintenant implémenter votre action WebApi comme suit:
[Route("api/[controller]")]
public class MyController : Controller
{
public MyDbContext _context { get; set; }
public MyController([FromServices] MyDbContext context)
{
_context = context;
}
[HttpGet]
public async IEnumerable<object> Get()
{
var returnObject = new List<dynamic>();
using (var cmd = _context.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "exec dbo.spGetSomeData";
cmd.CommandType = CommandType.StoredProcedure;
// set some parameters of the stored procedure
cmd.Parameters.Add(new SqlParameter("@someParam",
SqlDbType.TinyInt) { Value = 1 });
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = await cmd.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++) {
// one can modify the next line to
// if (dataReader.IsDBNull(iFiled))
// dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
// if one want don't fill the property for NULL
// returned from the database
dataRow.Add(
dataReader.GetName(iFiled),
dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
);
}
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
Le code ci-dessus s’exécute simplement avec exec dbo.spGetSomeData
et utilise dataRader pour lire tous les résultats et les sauvegarder dans l’objet dynamic
. Si vous appelez $.ajax
à partir de api/My
, vous obtiendrez les données renvoyées à partir de dbo.spGetSomeData
, que vous pourrez directement utiliser en code JavaScript. Le code ci-dessus est très transparent. Les noms des champs de l'ensemble de données renvoyés par dbo.spGetSomeData
seront les noms des propriétés dans le code JavaScript. Vous n'avez pas besoin de gérer les classes d'entités dans votre code C # en aucune façon. Votre code C # n'a pas de noms de champs renvoyés par la procédure stockée. Ainsi, si vous souhaitez prolonger/modifier le code de dbo.spGetSomeData
(renommer certains champs, ajouter de nouveaux champs), vous devrez uniquement ajuster votre code JavaScript, mais pas de code C #.
DbContext
a une propriété Database
qui contient une connexion à la base de données sur laquelle vous pouvez faire ce que vous voulez avec:
context.Database.SqlQuery<Foo>("exec [dbo].[GetFoo] @Bar = {0}", bar);
Cependant, plutôt que de le faire dans vos actions Web Api, je vous suggérerais d'ajouter une méthode à votre contexte ou à tout service/référentiel qui interagit avec votre contexte. Ensuite, appelez cette méthode dans votre action. Idéalement, vous souhaitez conserver toutes vos données SQL au même endroit.
Tout comme la réponse ci-dessus, vous pouvez simplement utiliser FromSQL () à la place de SqlQuery <> ().
context.Set().FromSql("[dbo].[GetFoo] @Bar = {0}", 45);
Pour la première approche de la base de données, vous devez utiliser la commande Scaffold-DbContext
Installer les packages Nuget Microsoft.EntityFrameworkCore.Tools et Microsoft.EntityFrameworkCore.SqlServer.Design
Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
mais cela n'obtiendra pas vos procédures stockées. Il est toujours en chantier, problème de suivi # 245
Mais, pour exécuter les procédures stockées, utilisez 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 l’exécution de requêtes SQL RAW ou de 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);
Utilisation du connecteur MySQL et du noyau Entity Framework 2.0
Mon problème était que je devenais une exception comme FX. Ex.Message = "La colonne requise 'body' 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 données pour cet appel spécifique.
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
OU avec paramètres
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();