Como chamar o procedimento armazenado no Entity Framework 6 (código primeiro)?


259

Eu sou muito novo no Entity Framework 6 e quero implementar procedimentos armazenados no meu projeto. Eu tenho um procedimento armazenado da seguinte maneira:

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
}

Meu problema é: como posso chamar o procedimento armazenado e passar parâmetros para ele?


Também estou interessado em saber isso. Idealmente, eu ignoraria o EF completamente e executaria TUDO através de nada além de procedimentos armazenados. Sou especialista em SQL, mas achei a EF muito frustrante de implementar.
David Britz

Respostas:


247

Você pode chamar um procedimento armazenado em sua DbContextclasse da seguinte maneira.

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

Mas se o procedimento armazenado retornar vários conjuntos de resultados como seu código de exemplo, você poderá ver este artigo útil no MSDN

Procedimentos armazenados com vários conjuntos de resultados


2
Obrigado @Alborz. você pode me fornecer alguns links sobre várias implementações do Stored Procedure no Entity Framework 6 Code First. Pesquisei em qualquer lugar da Web, mas não recebi nenhum artigo em que eu possa chamar diretamente um procedimento armazenado para os parâmetros IN e OUT. Obrigado pelo seu precioso tempo.
Jaan


8
Isso não parece funcionar com parâmetros. Parece precisar listar explicitamente os parâmetros como parte da consulta.
Mark

6
Sim, você precisa especificar os parâmetros como parte da consulta - "storedProcedureName @param1, @param2". Também o tipo de paramsé System.Data.SqlClient.SqlParameter[].
Oppa Gingham Style

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

152

Tudo que você precisa fazer é criar um objeto que tenha os mesmos nomes de propriedades que os resultados retornados pelo procedimento armazenado. Para o seguinte procedimento armazenado:

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

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

crie uma classe parecida com:

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

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

e chame o procedimento da seguinte maneira:

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

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

O resultado conterá uma lista de ResultForCampaignobjetos. Você pode ligar SqlQueryusando quantos parâmetros forem necessários.


2
Para situações pontuais, isso funcionaria muito bem. Acho que a definição do SProc deve ser fortemente associada à classe que herda do DBContext, em vez de sair nos "campos de trigo" do produto.
GoldBishop

50

Eu resolvi com ExecuteSqlCommand

Coloque seu próprio método como o meu no DbContext como suas próprias instâncias:

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

para que você possa ter um método no seu code-behind assim:

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

este é o meu 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

a esperança ajudou você


2
Você precisa especificar um comprimento nos parâmetros nchar para seu procedimento armazenado - caso contrário, eles terão apenas um caractere, como você encontrou.
22814 Dave W

@Mahdighafoorian Esta é uma resposta muito útil, muito obrigado! :)
Komengem

Essa sintaxe não requer modificação na ordem dos parâmetros do SProc, ou seja, posicionamento ordinal.
quer tocar

21

Usando seu exemplo, aqui estão duas maneiras de fazer isso:

1 - Use o mapeamento de procedimento armazenado

Observe que esse código funcionará com ou sem mapeamento. Se você desativar o mapeamento na entidade, o EF gerará uma instrução 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 - Ligue diretamente para o procedimento armazenado

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

Eu recomendo usar a primeira abordagem, pois você pode trabalhar diretamente com o objeto de departamento e não precisa criar um monte de objetos SqlParameter.


3
Tenha cuidado, é o segundo exemplo a mudança não é controlado pela DbContext
edtruant

EDIT.Use System.Data.Entity.DbSet <TEntity> .SqlQuery (String, Object []) em vez disso.
edtruant

@edtruant O dbContext parece rastrear a alteração. Para testar, observei db. <DbSet> .Count () antes e depois da instrução insert. Nos dois métodos, a contagem aumentou em um. Para completar, adicionei o método alternativo ao exemplo.
precisa saber é o seguinte

1
Não vejo nenhuma referência ao procedimento armazenado no primeiro exemplo.
Xr280xr

2
@ xr280xr o insert_department é referenciado na expressão modelBuilder na pergunta do OP. Essa é a vantagem de coisas mapeamento desta forma porque ele efetivamente funções da mesma maneira como se estivesse deixando EF gerar os inserção de atualização de declarações / / DELETE
Brian Vander Plaats

15

Você está usando o MapToStoredProcedures()que indica que está mapeando suas entidades para procedimentos armazenados. Ao fazer isso, é necessário deixar de lado o fato de que existe um procedimento armazenado e usá-lo contextnormalmente. Algo assim ( escrito no navegador, portanto não testado )

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

Se tudo o que você realmente está tentando fazer é chamar diretamente um procedimento armazenado, use SqlQuery


2
Obrigado qujck. Mas eu quero usar o procedimento armazenado. Eu dei apenas um código de exemplo para entender melhor.
214 Jaan

4
@Jaan - O código acima irá usar o procedimento armazenado. Você quer dizer que deseja chamar diretamente o procedimento armazenado?
qujck

sim. Você pode me dizer qual o melhor caminho? Chamando diretamente o procedimento armazenado ou o código acima que você forneceu?
213 Jaan

6
@ Jaan use o código que mostrei - o ORM deve ocultar a implementação subjacente - o uso do código acima garante que não importa para o resto do seu código se existe um procedimento armazenado ou não. Você pode até alterar o mapeamento do modelo para outro procedimento armazenado ou não ser um procedimento armazenado sem alterar mais nada.
qujck

4
@ Chazt3n A pergunta mostra os procedimentos armazenados sendo configurados a partir da linha .MapToStoredProcedures(s => . Uma chamada para Adddeve resolver para.Insert(i => i.HasName("insert_department")
qujck 16/04

12

Agora você também pode usar uma convenção que eu criei que permite chamar procedimentos armazenados (incluindo procedimentos armazenados retornando vários conjuntos de resultados), TVFs e UDFs escalares nativamente do EF.

Até o lançamento do Entity Framework 6.1, as funções de armazenamento (por exemplo, Funções com valor de tabela e Procedimentos armazenados) poderiam ser usadas no EF somente ao executar o Database First. Houve algumas soluções alternativas que tornaram possível invocar funções de armazenamento nos aplicativos Code First, mas você ainda não podia usar TVFs nas consultas Linq, que era uma das maiores limitações. No EF 6.1, a API de mapeamento foi tornada pública, o que (junto com alguns ajustes adicionais) tornou possível o uso de funções de armazenamento em seus aplicativos Code First.

Consulte Mais informação

Eu me esforcei bastante nas últimas duas semanas e aqui está - a versão beta da convenção que permite o uso de funções de armazenamento (ou seja, procedimentos armazenados, funções com valor de tabela etc.) em aplicativos que usam a abordagem Code First e o Entity Framework 6.1.1 ( ou mais recente). Estou mais do que feliz com as correções e os novos recursos incluídos nesta versão.

Leia mais .


Na verdade, desde a versão 4.0, você pode executar o SProcs sem o modelo. Você precisava executar instruções SQL brutas em vez da propriedade do objeto. Mesmo com o 6.1.x, você precisa usar SqlQuery <T> ou ExecuteSqlCommand para obter um efeito semelhante.
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 é um identificador, use um nome diferente.
yogihosting

2
O SaveChanges () aqui não é necessário. As alterações são confirmadas na chamada ExecuteSqlCommand ().
Xavier Poinas

10

Isso funciona para mim, retirando dados de um procedimento armazenado ao passar um parâmetro.

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

_db é o dbContext


9

Dê uma olhada neste link que mostra como funciona o mapeamento do EF 6 com procedimentos armazenados para fazer uma inserção, atualização e exclusão: http://msdn.microsoft.com/en-us/data/dn468673

Adição

Aqui está um ótimo exemplo para chamar um procedimento armazenado do Code First:

Digamos que você precise executar um Procedimento armazenado com um único parâmetro e que o Procedimento armazenado retorne um conjunto de dados que corresponda aos Estados da entidade, portanto, teremos isso:

var countryIso = "AR"; //Argentina

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

Agora, digamos que desejamos executar outro procedimento armazenado com dois parâmetros:

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

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

Observe que estamos usando a nomeação baseada em índice para parâmetros. Isso ocorre porque o Entity Framework agrupa esses parâmetros como objetos DbParameter para evitar problemas de injeção de SQL.

Espero que este exemplo ajude!


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

Funciona para mim primeiro no código. Ele retorna uma lista com propriedades correspondentes do modelo de exibição (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();

Atualizado para o contexto

Contexto é a instância da classe que Inherit DbContext como abaixo.

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

var Context = new  ApplicationDbContext();

Oi, Não consigo encontrar este Context.Database.SqlQuery <Model>, onde como eu posso fazer isso Context.TableName.SqlQuery (ProcName). que está dando problemas me
Marshall

@ Marshall, talvez você esteja usando o primeiro design do banco de dados. verifique este link stackoverflow.com/questions/11792018/…
reza.cse08

1

O passageiro irracional tem um projeto que permite que vários conjuntos de resultados sejam retornados de um processo armazenado usando a estrutura da entidade. Um de seus exemplos abaixo ....

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

Você pode passar parâmetros para sp_GetByIde buscar os resultados em ToList()ouFirstOrDefault();

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

0

se você quiser passar parâmetros de tabela para o procedimento armazenado, defina a propriedade TypeName necessária para seus parâmetros de tabela.

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

Isto é o que o EF (DB primeiro) gera na 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

Quando o EDMX criar esse tempo, se você selecionar a opção de procedimento armazenado na tabela, basta chamar o armazenamento processado usando o nome do procedimento ...

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

Descobri que a chamada de procedimentos armazenados na abordagem Code First não é conveniente. Eu prefiro usarDapper vez

O seguinte código foi escrito com Entity Framework:

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

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

O seguinte código foi escrito com Dapper:

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

Eu acredito que o segundo pedaço de código é mais simples de entender.


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

Nada precisa fazer ... quando você estiver criando o dbcontext para a primeira abordagem de código, inicialize o namespace abaixo da área da API fluente, faça uma lista de sp e use-a em outro local onde desejar.

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

Usando primeiro o código de estrutura MySql e Entity

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

Criar procedimento no 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;
//

Criar classe que contém valores do conjunto de resultados de retorno do procedimento armazenado

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

}

Adicionar classe no Dbcontext

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

    }

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

Chamar entidade no repositório

   var counts = _Dbcontext.SP_reslutclass.FromSql("call SP_Dasboarddata 
                    ('2019-12-03','2019-12-31')").ToList();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.