¿Cómo llamar al procedimiento almacenado en Entity Framework 6 (Code-First)?

260

Soy muy nuevo en Entity Framework 6 y quiero implementar procedimientos almacenados en mi proyecto. Tengo un procedimiento almacenado de la siguiente manera:

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 clase:

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
}

Mi problema es: ¿cómo puedo llamar al procedimiento almacenado y pasarle parámetros?

Jaan
fuente
Estoy interesado en saber eso también. Lo ideal sería saltear EF por completo y ejecutar TODO a través de nada más que procedimientos almacenados. Soy un experto en SQL, pero he encontrado que EF es muy frustrante de implementar.
David Britz

Respuestas:

247

Puede llamar a un procedimiento almacenado en su DbContextclase de la siguiente manera.

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

Pero si su procedimiento almacenado devuelve múltiples conjuntos de resultados como su código de muestra, puede ver este útil artículo en MSDN

Procedimientos almacenados con conjuntos de resultados múltiples

Alborz
fuente
2
Gracias @Alborz. ¿Pueden proporcionarme algunos enlaces sobre la implementación de varios procedimientos almacenados en Entity Framework 6 Code First? Busqué en todas partes en la web pero no obtuve ningún artículo donde pueda llamar directamente a un procedimiento almacenado para los parámetros IN y OUT. Gracias por tu valioso tiempo.
Jaan
2
Este artículo puede ser útil blogs.msdn.com/b/diego/archive/2012/01/10/…
Alborz
8
Esto no parece funcionar con parámetros. Parece que es necesario enumerar explícitamente los parámetros como parte de la consulta.
Mark
66
Sí, debe especificar los parámetros como parte de la consulta "storedProcedureName @param1, @param2". También el tipo de paramses System.Data.SqlClient.SqlParameter[].
Oppa Gingham Style
66
this.Database.SqlQuery<YourEntityType>("storedProcedureName @param1", new System.Data.SqlClient.SqlParameter("@param1", YourParam));
Ppp
152

Todo lo que tiene que hacer es crear un objeto que tenga los mismos nombres de propiedad que los resultados devueltos por el procedimiento almacenado. Para el siguiente procedimiento almacenado:

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

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

crear una clase que se vea así:

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

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

y luego llame al procedimiento haciendo lo siguiente:

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

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

El resultado contendrá una lista de ResultForCampaignobjetos. Puede llamar SqlQueryutilizando tantos parámetros como sea necesario.

Filipe Leite
fuente
2
Para situaciones puntuales, esto funcionaría muy bien. Encuentro que la definición SProc debe estar estrechamente unida a la clase que hereda de DBContext, en lugar de estar fuera de los "campos de trigo" del producto.
GoldBishop
51

Lo resolví con ExecuteSqlCommand

Pon tu propio método como el mío en DbContext como tus propias instancias:

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 pueda tener un método en su código subyacente como este:

[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 es mi 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

la esperanza te ayudó

Mahdi ghafoorian
fuente
2
Debe especificar una longitud en los parámetros de nchar para su procedimiento almacenado; de lo contrario, solo tienen un carácter, como ha encontrado.
Dave W
@Mahdighafoorian Esta es una respuesta muy útil, ¡muchas gracias! :)
Komengem
Esta sintaxis no requiere modificación del orden de los parámetros de SProc, en otras palabras, posicionamiento ordinal.
GoldBishop
21

Usando su ejemplo, aquí hay dos formas de lograr esto:

1 - Usar mapeo de procedimiento almacenado

Tenga en cuenta que este código funcionará con o sin mapeo. Si desactiva la asignación en la entidad, EF generará una instrucción de inserción + selección.

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 - Llamar directamente al procedimiento almacenado

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

Recomiendo usar el primer enfoque, ya que puede trabajar con el objeto de departamento directamente y no tener que crear un montón de objetos SqlParameter.

Brian Vander Plaats
fuente
3
Tenga cuidado, es el segundo ejemplo, el cambio no es rastreado por dbContext
edtruant
EDITAR. Utilice el System.Data.Entity.DbSet <TEntity> .SqlQuery (String, Object []) en su lugar.
edtruant
@edtruant El dbContext parece rastrear el cambio. Para probar, miré db. <DbSet> .Count () antes y después de la instrucción de inserción. En ambos métodos, el recuento aumentó en uno. Para completar, agregué el método alternativo al ejemplo.
Brian Vander Plaats
1
No veo ninguna referencia al procedimiento almacenado en el primer ejemplo.
xr280xr
2
@ xr280xr el insert_department se hace referencia en la expresión modelBuilder en la pregunta del OP. Esa es la ventaja de mapear las cosas de esta manera porque efectivamente funciona de la misma manera que si estuvieras dejando que EF generara las declaraciones de inserción / actualización / eliminación
Brian Vander Plaats
15

Está utilizando lo MapToStoredProcedures()que indica que está asignando sus entidades a procedimientos almacenados, al hacer esto debe dejar de lado el hecho de que hay un procedimiento almacenado y usar el de contextforma normal. Algo como esto ( escrito en el navegador para que no se pruebe )

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

Si todo lo que realmente intenta hacer es llamar directamente a un procedimiento almacenado, entonces use SqlQuery

qujck
fuente
2
Gracias qujck Pero quiero usar el procedimiento almacenado. Solo he dado un código de muestra para que sea más fácil de entender.
Jaan
44
@Jaan - El código anterior se utilice el procedimiento almacenado. ¿Quiere decir que desea llamar directamente al procedimiento almacenado?
Qujck
si. ¿Puedes decirme qué camino es mejor? ¿Llamando directamente al procedimiento almacenado o al código anterior que ha proporcionado?
Jaan
66
@Jaan usa el código que he mostrado (el ORM está destinado a ocultar la implementación subyacente). El uso del código anterior garantiza que no le importe al resto del código si hay un procedimiento almacenado o no. Incluso puede cambiar la asignación del modelo a otro procedimiento almacenado o no ser un procedimiento almacenado sin cambiar nada más.
Qujck
44
@ Chazt3n La pregunta muestra los procedimientos almacenados que se configuran desde la línea .MapToStoredProcedures(s => . Una llamada a Adddebe resolverse a.Insert(i => i.HasName("insert_department")
qujck
12

Ahora también puede usar una convención que creé que permite invocar procedimientos almacenados (incluidos los procedimientos almacenados que devuelven múltiples conjuntos de resultados), TVF y UDF escalares de forma nativa desde EF.

Hasta que se lanzó Entity Framework 6.1, las funciones de almacenamiento (es decir, Funciones con valores de tabla y Procedimientos almacenados) solo se podían usar en EF cuando se hacía primero la base de datos. Hubo algunas soluciones que permitieron invocar las funciones de la tienda en las aplicaciones de Code First, pero aún no podía usar TVF en las consultas de Linq, que era una de las mayores limitaciones. En EF 6.1, la API de mapeo se hizo pública, lo que (junto con algunos ajustes adicionales) hizo posible el uso de funciones de almacenamiento en sus aplicaciones Code First.

Lee mas

Presioné bastante durante las últimas dos semanas y aquí está: la versión beta de la convención que permite usar funciones de almacenamiento (es decir, procedimientos almacenados, funciones con valores de tabla, etc.) en aplicaciones que usan el enfoque Code First y Entity Framework 6.1.1 ( o mas nuevo). Estoy más que satisfecho con las correcciones y las nuevas funciones que se incluyen en esta versión.

Leer más .

Pawel
fuente
En realidad, desde 4.0, puede ejecutar SProcs sin el modelo. Necesitabas ejecutar sentencias de SQL sin formato en lugar de la propiedad del objeto. Incluso con 6.1.x, debe usar SqlQuery <T> o ExecuteSqlCommand para obtener un efecto similar.
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;  
Shiraj Momin
fuente
1
params es un identificador que usa un nombre diferente.
yogihosting
2
El SaveChanges () aquí no es necesario. Los cambios se confirman en la llamada ExecuteSqlCommand ().
Xavier Poinas
10

Esto funciona para mí retirando datos de un procedimiento almacenado mientras pasa un parámetro.

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

_db es el dbContext

Tom Stickel
fuente
9

Eche un vistazo a este enlace que muestra cómo funciona la asignación de EF 6 con procedimientos almacenados para realizar una inserción, actualización y eliminación: http://msdn.microsoft.com/en-us/data/dn468673

Adición

Aquí hay un gran ejemplo para llamar a un procedimiento almacenado desde Code First:

Digamos que tiene que ejecutar un Procedimiento almacenado con un solo parámetro, y que el Procedimiento almacenado devuelve un conjunto de datos que coinciden con los Estados de entidad, por lo que tendremos esto:

var countryIso = "AR"; //Argentina

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

Ahora digamos que queremos ejecutar otro procedimiento almacenado con dos 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 nombres basados ​​en índices para los parámetros. Esto se debe a que Entity Framework envolverá estos parámetros como objetos DbParameter para evitar cualquier problema de inyección SQL.

Espero que este ejemplo ayude!

Gabriel Andrés Brancolini
fuente
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();
}
MD Delower Hossain
fuente
4

Funciona para mí al código primero. Devuelve una lista con la propiedad coincidente del modelo de vista (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();

Actualizado para contexto

El contexto es la instancia de la clase que hereda DbContext como a continuación.

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

var Context = new  ApplicationDbContext();
reza.cse08
fuente
Hola, no puedo encontrar este Context.Database.SqlQuery <Model>, donde como puedo hacer este Context.TableName.SqlQuery (ProcName). lo que me está dando problemas
Marshall
@ Marshall, tal vez esté utilizando el primer diseño de la base de datos. consulte este enlace stackoverflow.com/questions/11792018/…
reza.cse08
1

El pasajero sin sentido tiene un proyecto que permite que se devuelvan conjuntos de resultados múltiples desde un proceso almacenado utilizando el marco de la entidad. Uno de sus ejemplos a continuación ...

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>();
}
Dib
fuente
1

Puede pasar parámetros sp_GetByIdy obtener los resultados en ToList()oFirstOrDefault();

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

si desea pasar parámetros de tabla al procedimiento almacenado, debe establecer necesariamente la propiedad TypeName para sus parámetros de tabla.

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

Esto es lo que EF (DB primero) genera en la clase DbContext:

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

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

Cuando EDMX cree este tiempo si selecciona almacenado procesado en la opción de selección de tabla, simplemente llame a la tienda procesada usando el nombre del procedimiento ...

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.
Shafiq Rabbi
fuente
0

Encontré que llamar a procedimientos almacenados en el enfoque de Code First no es conveniente. Prefiero usar Dapperen su lugar

El siguiente código fue escrito con Entity Framework:

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

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

El siguiente código fue escrito con Dapper:

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

Creo que la segunda parte del código es más simple de entender.

Vladislav Furdak
fuente
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();
        }
mattylantz
fuente
0

Nada tiene que hacer ... cuando está creando dbcontext para el primer enfoque de código, inicialice el espacio de nombres debajo del área API fluida, haga una lista de sp y úsela en otro lugar donde desee.

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; }
}
SHUBHASIS MAHATA
fuente
0

Usando MySql y Entity Framework Code primer enfoque:

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();
Hari Lakkakula
fuente
0

Crear procedimiento en 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;
//

Crear clase que contiene valores de conjunto de resultados de retorno de procedimientos almacenados

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

}

Agregar clase en Dbcontext

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

    }

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

Entidad de llamada en repositorio

   var counts = _Dbcontext.SP_reslutclass.FromSql("call SP_Dasboarddata 
                    ('2019-12-03','2019-12-31')").ToList();
Hari Lakkakula
fuente