web-dev-qa-db-fra.com

Comment appeler une procédure stockée SQL en utilisant EntityFramework 7 et Asp.Net 5

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.

 enter image description here

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?

5
Subrata Sarkar

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 #.

11
Oleg

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.

5
Chris Pratt

Tout comme la réponse ci-dessus, vous pouvez simplement utiliser FromSQL () à la place de SqlQuery <> ().

context.Set().FromSql("[dbo].[GetFoo] @Bar = {0}", 45);
1
TGarrett

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

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(); 
0
chri3g91