Comment appeler la procédure stockée dans Entity Framework 6 (Code-First)?


260

Je suis très nouveau dans Entity Framework 6 et je souhaite implémenter des procédures stockées dans mon projet. J'ai une procédure stockée comme suit:

ALTER PROCEDURE [dbo].[insert_department]
    @Name [varchar](100)
AS
BEGIN
    INSERT [dbo].[Departments]([Name])
    VALUES (@Name)

    DECLARE @DeptId int

    SELECT @DeptId = [DeptId]
    FROM [dbo].[Departments]
    WHERE @@ROWCOUNT > 0 AND [DeptId] = SCOPE_IDENTITY()

    SELECT t0.[DeptId]
    FROM [dbo].[Departments] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[DeptId] = @DeptId
END

Department classe:

public class Department
{
    public int DepartmentId { get; set; }       
    public string Name { get; set; }
}

modelBuilder 
.Entity<Department>() 
.MapToStoredProcedures(s => 
s.Update(u => u.HasName("modify_department") 
               .Parameter(b => b.Department, "department_id") 
               .Parameter(b => b.Name, "department_name")) 
 .Delete(d => d.HasName("delete_department") 
               .Parameter(b => b.DepartmentId, "department_id")) 
 .Insert(i => i.HasName("insert_department") 
               .Parameter(b => b.Name, "department_name")));

protected void btnSave_Click(object sender, EventArgs e)
{
    string department = txtDepartment.text.trim();

    // here I want to call the stored procedure to insert values
}

Mon problème est: comment puis-je appeler la procédure stockée et y passer des paramètres?


Ça m'intéresse de savoir ça aussi. Idéalement, je sauterais EF complètement et exécuterais TOUT à travers les procédures stockées. Je suis un expert en SQL mais j'ai trouvé EF très frustrant à mettre en œuvre.
David Britz

Réponses:


247

Vous pouvez appeler une procédure stockée dans votre DbContextclasse comme suit.

this.Database.SqlQuery<YourEntityType>("storedProcedureName",params);

Mais si votre procédure stockée renvoie plusieurs jeux de résultats comme exemple de code, vous pouvez voir cet article utile sur MSDN

Procédures stockées avec plusieurs jeux de résultats


2
Merci @Alborz. pouvez-vous s'il vous plaît me fournir quelques liens concernant diverses implémentations de la procédure stockée dans Entity Framework 6 Code First. J'ai cherché partout sur le Web, mais je n'ai trouvé aucun article où je peux appeler directement une procédure stockée pour les paramètres IN et OUT. Merci pour votre précieux temps.
Jaan

2
Cet article peut être utile blogs.msdn.com/b/diego/archive/2012/01/10/…
Alborz

8
Cela ne semble pas fonctionner avec les paramètres. Il semble devoir répertorier explicitement les paramètres dans le cadre de la requête.
Mark

6
Oui, vous devez spécifier les paramètres dans le cadre de la requête - "storedProcedureName @param1, @param2". Le type paramsest également System.Data.SqlClient.SqlParameter[].
Oppa Gingham Style

6
this.Database.SqlQuery<YourEntityType>("storedProcedureName @param1", new System.Data.SqlClient.SqlParameter("@param1", YourParam));
Ppp

152

Il vous suffit de créer un objet qui a les mêmes noms de propriété que les résultats renvoyés par la procédure stockée. Pour la procédure stockée suivante:

    CREATE PROCEDURE [dbo].[GetResultsForCampaign]  
    @ClientId int   
    AS
    BEGIN
    SET NOCOUNT ON;

    SELECT AgeGroup, Gender, Payout
    FROM IntegrationResult
    WHERE ClientId = @ClientId
    END

créer une classe qui ressemble à:

    public class ResultForCampaign
    {
        public string AgeGroup { get; set; }

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

puis appelez la procédure en procédant comme suit:

    using(var context = new DatabaseContext())
    {
            var clientIdParameter = new SqlParameter("@ClientId", 4);

            var result = context.Database
                .SqlQuery<ResultForCampaign>("GetResultsForCampaign @ClientId", clientIdParameter)
                .ToList();
    }

Le résultat contiendra une liste d' ResultForCampaignobjets. Vous pouvez appeler en SqlQueryutilisant autant de paramètres que nécessaire.


2
Pour des situations ponctuelles, cela fonctionnerait très bien. Je trouve que la définition SProc devrait être étroitement couplée avec la classe qui hérite de DBContext, plutôt que dans les "champs de blé" du produit.
GoldBishop

51

Je l'ai résolu avec ExecuteSqlCommand

Mettez votre propre méthode comme la mienne dans DbContext comme vos propres instances:

public void addmessage(<yourEntity> _msg)
{
    var date = new SqlParameter("@date", _msg.MDate);
    var subject = new SqlParameter("@subject", _msg.MSubject);
    var body = new SqlParameter("@body", _msg.MBody);
    var fid = new SqlParameter("@fid", _msg.FID);
    this.Database.ExecuteSqlCommand("exec messageinsert @Date , @Subject , @Body , @Fid", date,subject,body,fid);
}

vous pouvez donc avoir une méthode dans votre code-behind comme ceci:

[WebMethod] //this method is static and i use web method because i call this method from client side
public static void AddMessage(string Date, string Subject, string Body, string Follower, string Department)
{
    try
    {
        using (DBContext reposit = new DBContext())
        {
            msge <yourEntity> Newmsg = new msge();
            Newmsg.MDate = Date;
            Newmsg.MSubject = Subject.Trim();
            Newmsg.MBody = Body.Trim();
            Newmsg.FID= 5;
            reposit.addmessage(Newmsg);
        }
    }
    catch (Exception)
    {
        throw;
    }
}

c'est mon SP:

Create PROCEDURE dbo.MessageInsert

    @Date nchar["size"],
    @Subject nchar["size"],
    @Body nchar["size"],
    @Fid int
AS
    insert into Msg (MDate,MSubject,MBody,FID) values (@Date,@Subject,@Body,@Fid)
    RETURN

l'espoir vous a aidé


2
Vous devez spécifier une longueur sur les paramètres nchar à votre procédure stockée - sinon ils ne font qu'un caractère, comme vous l'avez trouvé.
Dave W

@Mahdighafoorian Ceci est une réponse très utile, merci beaucoup! :)
Komengem

Cette syntaxe ne nécessite aucune modification de l'ordre des paramètres du SProc, c'est-à-dire du positionnement ordinal.
GoldBishop

21

En utilisant votre exemple, voici deux façons d'accomplir ceci:

1 - Utiliser le mappage de procédure stockée

Notez que ce code fonctionnera avec ou sans mappage. Si vous désactivez le mappage sur l'entité, EF générera une instruction insert + select.

protected void btnSave_Click(object sender, EventArgs e)
{
     using (var db = DepartmentContext() )
     {
        var department = new Department();

        department.Name = txtDepartment.text.trim();

        db.Departments.add(department);
        db.SaveChanges();

        // EF will populate department.DepartmentId
        int departmentID = department.DepartmentId;
     }
}

2 - Appelez directement la procédure stockée

protected void btnSave_Click(object sender, EventArgs e)
{
     using (var db = DepartmentContext() )
     {
        var name = new SqlParameter("@name", txtDepartment.text.trim());

        //to get this to work, you will need to change your select inside dbo.insert_department to include name in the resultset
        var department = db.Database.SqlQuery<Department>("dbo.insert_department @name", name).SingleOrDefault();

       //alternately, you can invoke SqlQuery on the DbSet itself:
       //var department = db.Departments.SqlQuery("dbo.insert_department @name", name).SingleOrDefault();

        int departmentID = department.DepartmentId;
     }
}

Je recommande d'utiliser la première approche, car vous pouvez travailler directement avec l'objet département et ne pas avoir à créer un groupe d'objets SqlParameter.


3
Attention, est le deuxième exemple, le changement n'est pas suivi par le dbContext
edtruant

EDIT.Utilisez plutôt System.Data.Entity.DbSet <TEntity> .SqlQuery (String, Object []).
edtruant

@edtruant Le dbContext semble suivre le changement. Pour tester, j'ai regardé db. <DbSet> .Count () avant et après l'instruction d'insertion. Dans les deux méthodes, le nombre a augmenté d'une unité. Pour être complet, j'ai ajouté la méthode alternative à l'exemple.
Brian Vander Plaats

1
Je ne vois aucune référence à la procédure stockée dans le premier exemple.
xr280xr

2
@ xr280xr le insert_department est référencé dans l'expression modelBuilder dans la question de l'OP. C'est l'avantage de mapper les choses de cette façon, car cela fonctionne de la même manière que si vous laissiez EF générer les instructions d'insertion / mise à jour / suppression
Brian Vander Plaats

15

Vous utilisez MapToStoredProcedures()ce qui indique que vous mappez vos entités aux procédures stockées, lorsque vous faites cela, vous devez laisser de côté le fait qu'il existe une procédure stockée et utiliser la contextcomme d'habitude. Quelque chose comme ça ( écrit dans le navigateur, donc non testé )

using(MyContext context = new MyContext())
{
    Department department = new Department()
    {
        Name = txtDepartment.text.trim()
    };
    context.Set<Department>().Add(department);
}

Si tout ce que vous essayez de faire est d'appeler directement une procédure stockée, utilisez SqlQuery


2
Merci qujck. Mais je veux utiliser la procédure stockée. J'ai donné juste un exemple de code pour faciliter la compréhension.
Jaan

4
@Jaan - Le code ci - dessus utilisera la procédure stockée. Voulez-vous dire que vous souhaitez appeler directement la procédure stockée?
qujck

Oui. Pouvez-vous s'il vous plaît me dire quelle est la meilleure. Vous appelez directement la procédure stockée ou le code ci-dessus que vous avez donné?
Jaan

6
@Jaan utilise le code que j'ai montré - l'ORM est censé cacher l'implémentation sous-jacente - en utilisant le code ci-dessus, cela n'a aucune importance pour le reste de votre code, qu'il y ait ou non une procédure stockée. Vous pouvez même changer le mappage de modèle en une autre procédure stockée ou ne pas être une procédure stockée sans rien changer d'autre.
qujck

4
@ Chazt3n La question montre les procédures stockées en cours de configuration à partir de la ligne .MapToStoredProcedures(s => . Un appel à Adddevrait résoudre.Insert(i => i.HasName("insert_department")
qujck

12

Vous pouvez maintenant également utiliser une convention que j'ai créée qui permet d'appeler des procédures stockées (y compris des procédures stockées renvoyant plusieurs jeux de résultats), des TVF et des FDU scalaires en mode natif depuis EF.

Jusqu'à la sortie d'Entity Framework 6.1, les fonctions de stockage (c'est-à-dire les fonctions de valeur de table et les procédures stockées) ne pouvaient être utilisées dans EF que lors de la première utilisation de la base de données. Certaines solutions de contournement ont permis d'appeler des fonctions de magasin dans les applications Code First, mais vous ne pouviez toujours pas utiliser les TVF dans les requêtes Linq, ce qui était l'une des plus grandes limitations. Dans EF 6.1, l'API de mappage a été rendue publique, ce qui (avec quelques ajustements supplémentaires) a permis d'utiliser les fonctions de stockage dans vos applications Code First.

Lire la suite

J'ai poussé assez fort au cours des deux dernières semaines et la voici - la version bêta de la convention qui permet d'utiliser les fonctions de stockage (c'est-à-dire les procédures stockées, les fonctions de valeur de table, etc.) dans les applications qui utilisent l'approche Code First et Entity Framework 6.1.1 ( Ou plus récent). Je suis plus que satisfait des correctifs et des nouvelles fonctionnalités inclus dans cette version.

Lisez plus .


En fait, depuis 4.0, vous pouvez exécuter SProcs sans le modèle. Vous deviez exécuter des instructions SQL brutes au lieu de la propriété d'objet. Même avec 6.1.x, vous devez utiliser SqlQuery <T> ou ExecuteSqlCommand pour obtenir un effet similaire.
GoldBishop

10
object[] xparams = {
            new SqlParameter("@ParametterWithNummvalue", DBNull.Value),
            new SqlParameter("@In_Parameter", "Value"),
            new SqlParameter("@Out_Parameter", SqlDbType.Int) {Direction = ParameterDirection.Output}};

        YourDbContext.Database.ExecuteSqlCommand("exec StoreProcedure_Name @ParametterWithNummvalue, @In_Parameter, @Out_Parameter", xparams);
        var ReturnValue = ((SqlParameter)params[2]).Value;  

1
params est un identifiant utilisant un nom différent.
yogihosting du

2
Le SaveChanges () ici n'est pas nécessaire. Les modifications sont validées lors de l'appel ExecuteSqlCommand ().
Xavier Poinas

10

Cela fonctionne pour moi en retirant les données d'une procédure stockée tout en passant un paramètre.

var param = new SqlParameter("@datetime", combinedTime);
var result = 
        _db.Database.SqlQuery<QAList>("dbo.GetQAListByDateTime @datetime", param).ToList();

_db est le dbContext


9

Jetez un œil à ce lien qui montre comment fonctionne le mappage d'EF 6 avec les procédures stockées pour effectuer une insertion, une mise à jour et une suppression: http://msdn.microsoft.com/en-us/data/dn468673

Une addition

Voici un excellent exemple pour appeler une procédure stockée à partir de Code First:

Disons que vous devez exécuter une procédure stockée avec un seul paramètre, et que la procédure stockée renvoie un ensemble de données qui correspondent aux états de l'entité, nous aurons donc ceci:

var countryIso = "AR"; //Argentina

var statesFromArgentina = context.Countries.SqlQuery(
                                      "dbo.GetStatesFromCountry @p0", countryIso
                                                    );

Disons maintenant que nous souhaitons exécuter une autre procédure stockée avec deux paramètres:

var countryIso = "AR"; //Argentina
var stateIso = "RN"; //Río Negro

var citiesFromRioNegro = context.States.SqlQuery(
                            "dbo.GetCitiesFromState @p0, @p1", countryIso, stateIso
                          );

Notez que nous utilisons une dénomination basée sur un index pour les paramètres. En effet, Entity Framework encapsule ces paramètres sous forme d'objets DbParameter pour vous afin d'éviter tout problème d'injection SQL.

J'espère que cet exemple vous aidera!


6
public IList<Models.StandardRecipeDetail> GetRequisitionDetailBySearchCriteria(Guid subGroupItemId, Guid groupItemId)
{
    var query = this.UnitOfWork.Context.Database.SqlQuery<Models.StandardRecipeDetail>("SP_GetRequisitionDetailBySearchCriteria @SubGroupItemId,@GroupItemId",
    new System.Data.SqlClient.SqlParameter("@SubGroupItemId", subGroupItemId),
    new System.Data.SqlClient.SqlParameter("@GroupItemId", groupItemId));
    return query.ToList();
}

4

Cela fonctionne pour moi au code d'abord. Il retourne une liste avec la propriété correspondante du modèle de vue (StudentChapterCompletionViewModel)

var studentIdParameter = new SqlParameter
{
     ParameterName = "studentId",
     Direction = ParameterDirection.Input,
     SqlDbType = SqlDbType.BigInt,
     Value = studentId
 };

 var results = Context.Database.SqlQuery<StudentChapterCompletionViewModel>(
                "exec dbo.sp_StudentComplettion @studentId",
                 studentIdParameter
                ).ToList();

Mis à jour pour le contexte

Le contexte est l'instance de la classe qui hérite de DbContext comme ci-dessous.

public class ApplicationDbContext : DbContext
{
    public DbSet<City> City { get; set; }
}

var Context = new  ApplicationDbContext();

Salut, je ne trouve pas ce Context.Database.SqlQuery <Model>, où comme je peux le faire Context.TableName.SqlQuery (ProcName). ce qui me donne des problèmes
Marshall

@Marshall, vous utilisez peut-être la première conception de la base de données. veuillez vérifier ce lien stackoverflow.com/questions/11792018/…
reza.cse08

1

Mindless passager a un projet qui permet de renvoyer plusieurs ensembles de résultats à partir d'un processus stocké à l'aide du cadre d'entité. Un de ses exemples ci-dessous ....

using (testentities te = new testentities())
{
    //-------------------------------------------------------------
    // Simple stored proc
    //-------------------------------------------------------------
    var parms1 = new testone() { inparm = "abcd" };
    var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
    var r1 = results1.ToList<TestOneResultSet>();
}

1

Vous pouvez passer des paramètres sp_GetByIdet récupérer les résultats dans ToList()ouFirstOrDefault();

var param  = new SqlParameter("@id", 106);
var result = dbContext
               .Database
               .SqlQuery<Category>("dbo.sp_GetById @id", param)
               .FirstOrDefault();

0

si vous voulez passer des paramètres de table dans une procédure stockée, vous devez définir la propriété TypeName pour vos paramètres de table.

SqlParameter codesParam = new SqlParameter(CODES_PARAM, SqlDbType.Structured);
            SqlParameter factoriesParam = new SqlParameter(FACTORIES_PARAM, SqlDbType.Structured);

            codesParam.Value = tbCodes;
            codesParam.TypeName = "[dbo].[MES_CodesType]";
            factoriesParam.Value = tbfactories;
            factoriesParam.TypeName = "[dbo].[MES_FactoriesType]";


            var list = _context.Database.SqlQuery<MESGoodsRemain>($"{SP_NAME} {CODES_PARAM}, {FACTORIES_PARAM}"
                , new SqlParameter[] {
                   codesParam,
                   factoriesParam
                }
                ).ToList();

0

C'est ce que EF (DB en premier) génère dans la classe DbContext:

public ObjectResult<int> Insert_Department(string department)
{
    var departmentParameter = new ObjectParameter("department", department);

    return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<int>("insert_department", departmentParameter);
}

0

Lorsque EDMX crée cette fois-ci si vous sélectionnez l'option de sélection de procédure stockée dans la table, puis appelez simplement la procédure de stockage en utilisant le nom de procédure ...

var num1 = 1; 
var num2 = 2; 

var result = context.proc_name(num1,num2).tolist();// list or single you get here.. using same thing you can call insert,update or delete procedured.

0

J'ai trouvé que l'appel des procédures stockées dans l'approche Code First n'est pas pratique. Je préfère utiliserDapper place

Le code suivant a été écrit avec Entity Framework:

var clientIdParameter = new SqlParameter("@ClientId", 4);

var result = context.Database
.SqlQuery<ResultForCampaign>("GetResultsForCampaign @ClientId", clientIdParameter)
.ToList();

Le code suivant a été écrit avec Dapper:

return Database.Connection.Query<ResultForCampaign>(
            "GetResultsForCampaign ",
            new
            {
                ClientId = 4
            },
            commandType: CommandType.StoredProcedure);

Je crois que le deuxième morceau de code est plus simple à comprendre.


0
public static string ToSqlParamsString(this IDictionary<string, string> dict)
        {
            string result = string.Empty;
            foreach (var kvp in dict)
            {
                result += $"@{kvp.Key}='{kvp.Value}',";
            }
            return result.Trim(',', ' ');
        }

public static List<T> RunSproc<T>(string sprocName, IDictionary<string, string> parameters)
        {
            string command = $"exec {sprocName} {parameters.ToSqlParamsString()}";
            return Context.Database.SqlQuery<T>(command).ToList();
        }

0

Rien à faire ... lorsque vous créez dbcontext pour le code, approchez d'abord l'initialisation de l'espace de noms sous la zone API courante, faites la liste de sp et utilisez-la à 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

Utilisation de MySql et du code cadre Entity first Approche:

public class Vw_EMIcount
{
    public int EmiCount { get; set; }
    public string Satus { get; set; }
}

var result = context.Database.SqlQuery<Vw_EMIcount>("call EMIStatus('2018-3-01' ,'2019-05-30')").ToList();

0

Créer une procédure dans MYsql.

delimiter //
create procedure SP_Dasboarddata(fromdate date, todate date)
begin
select count(Id) as count,date,status,sum(amount) as amount from 
details
where (Emidate between fromdate and todate)
group by date ,status;
END;
//

Créer une classe qui contient des valeurs d'ensemble de résultats de retour de procédure stockée

[Table("SP_reslutclass")]
public  class SP_reslutclass
{
    [Key]
    public int emicount { get; set; }
    public DateTime Emidate { get; set; }
    public int ? Emistatus { get; set; }
    public int emiamount { get; set; }

}

Ajouter une classe dans Dbcontext

  public  class ABCDbContext:DbContext
{
    public ABCDbContext(DbContextOptions<ABCDbContext> options)
       : base(options)
    {

    }

 public DbSet<SP_reslutclass> SP_reslutclass { get; set; }
}

Appeler l'entité dans le référentiel

   var counts = _Dbcontext.SP_reslutclass.FromSql("call SP_Dasboarddata 
                    ('2019-12-03','2019-12-31')").ToList();
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.