如何在Entity Framework 6(Code-First)中调用存储过程?

304

我对Entity Framework 6非常陌生,并且希望在我的项目中实现存储过程。我有以下存储过程:

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

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
}

我的问题是:如何调用存储过程并向其中传递参数?


我也对此很感兴趣。理想情况下,我会完全跳过EF,而只通过存储过程运行一切。虽然我是SQL专家,但我发现实现EF非常令人沮丧。 - David Britz
22个回答

280
您可以如下在DbContext类中调用存储过程。
this.Database.SqlQuery<YourEntityType>("storedProcedureName",params);

但是如果你的存储过程返回多个结果集,就像你的示例代码一样,那么你可以参考MSDN上这篇有用的文章。

具有多个结果集的存储过程


2
谢谢@Alborz。能否请您提供一些关于Entity Framework 6 Code First中存储过程不同实现方式的链接?我已经在网上搜索了很久,但没有找到任何可以直接调用输入输出参数的存储过程文章。非常感谢您宝贵的时间。 - Jaan
2
这篇文章可能会有所帮助:http://blogs.msdn.com/b/diego/archive/2012/01/10/how-to-execute-stored-procedures-sqlquery-in-the-dbcontext-api.aspx - Alborz
9
这似乎不能使用参数。它好像需要将参数明确列为查询的一部分。 - Mark
7
是的,您需要将参数作为查询的一部分进行指定 - "storedProcedureName @param1, @param2"。另外,params 的类型是 System.Data.SqlClient.SqlParameter[] - Oppa Gingham Style
6
这段代码意思是调用名为"storedProcedureName"的存储过程,并传递一个名为"YourParam"的参数。其中,"YourEntityType"表示你的实体类型。 - Ppp
显示剩余12条评论

169

你需要做的就是创建一个对象,该对象具有存储过程返回结果的相同属性名称。对于以下存储过程:

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

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

创建一个长这样的类:

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

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

然后按照以下方式调用该过程:

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

            var result = context.Database
                .SqlQuery<ResultForCampaign>("GetResultsForCampaign @ClientId", clientIdParameter)
                .ToList();
    }
结果将包含一列 ResultForCampaign 对象。您可以使用尽可能多的参数调用 SqlQuery

2
对于一次性的情况,这将非常有效。我发现SProc定义应该与继承自DBContext的类紧密耦合,而不是在产品的“麦田”之外。 - GoldBishop

63

我使用ExecuteSqlCommand解决了它。

将你自己的方法像我的一样放在DbContext中作为你自己的实例:

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

这样你就可以在代码后台拥有如下方法:

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

这是我的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

希望对你有所帮助。


3
你需要在存储过程的nchar参数上指定长度,否则它们只有一个字符长,就像你已经发现的那样。 - Dave W
@Mahdighafoorian 这是一个非常有用的答案,非常感谢! :) - Komengem
这个语法不需要修改SProc参数的顺序,换句话说是按位置排序。 - GoldBishop
我正在寻找的答案,谢谢。 - raw_hitt
这是来自书中的正确答案。 - Leandro Bardelli

25

使用您的示例,有两种方法可以实现此目标:

方法1:使用存储过程映射

请注意,该代码可以在有或没有映射的情况下工作。如果在实体上关闭映射,则 EF 将生成一个插入 + 选择语句。

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

方法二:直接调用存储过程

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

我建议使用第一种方法,因为你可以直接使用部门对象进行操作,而不必创建大量的SqlParameter对象。


3
请注意,第二个例子中的更改不会被dbContext跟踪。 - edtruant
请使用 System.Data.Entity.DbSet<TEntity>.SqlQuery(String, Object[]) 替代。 - edtruant
@edtruant dbContext 确实似乎跟踪了更改。为了测试,我查看了插入语句之前和之后的 db.<DbSet>.Count()。在两种方法中,计数都增加了一个。为了完整起见,我在示例中添加了备用方法。 - Brian Vander Plaats
3
在第一个示例中,我没有看到任何关于储存过程的引用。 - xr280xr
2
@xr280xr,insert_department在OP的问题中被引用于modelBuilder表达式中。这种映射方式的优点在于它的功能与让EF生成插入/更新/删除语句的方式相同。 - Brian Vander Plaats
如果您要手动调用存储过程,那么使用EF就没有意义了。 - binki

16

你正在使用MapToStoredProcedures(),这表示你正在将实体映射到存储过程中。当你这样做时,你需要放弃对存储过程的概念,并像正常情况下一样使用context

就像这样(写入浏览器未经测试

using(MyContext context = new MyContext())
{
    Department department = new Department()
    {
        Name = txtDepartment.text.trim()
    };
    context.Set<Department>().Add(department);
}
如果你只是想直接调用存储过程,那么使用 SqlQuery

2
谢谢你的快速回复。但我想使用存储过程。我只是提供了一个方便理解的示例代码。 - Jaan
4
以上的代码 使用存储过程。您是不是想直接调用存储过程? - qujck
6
请使用我展示的代码 - ORM旨在隐藏底层实现 - 使用上面的代码可确保对于您的其余代码而言,是否存在存储过程并不重要。您甚至可以更改模型映射以使用另一个存储过程或不使用存储过程,而无需更改其他任何内容。 - qujck
4
这个问题展示了存储过程从.MapToStoredProcedures(s =>这一行进行配置。对于Add的调用应该解析为.Insert(i => i.HasName("insert_department") - qujck
试图从DataTables转向使用EF,感谢这个答案中的“拍我脑袋一下”的提示。 - Stryder
显示剩余2条评论

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

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

1
params是一个标识符,请使用其他名称。 - yogihosting
3
这里的SaveChanges()不必要。 在ExecuteSqlCommand()调用时提交更改。 - Xavier Poinas

14
你现在还可以使用我创建的一种约定,从EF中本地调用存储过程(包括返回多个结果集的存储过程)、TVFs和标量UDFs。
直到Entity Framework 6.1发布之前,只有在使用数据库优先时才能在EF中使用存储函数(即表值函数和存储过程)。虽然存在一些变通方法使得在Code First应用程序中调用存储函数成为可能,但仍然无法在Linq查询中使用TVFs,这是最大的限制之一。在EF 6.1中,映射API被公开,这使得在Code First应用程序中使用存储函数成为可能(连同一些其他微调)。 阅读更多
在过去的两周里,我非常努力地推进了这项工作,现在它已经出来了——beta版本的约定,可以在使用Code First方法和Entity Framework 6.1.1(或更高版本)的应用程序中使用存储函数(即存储过程、表值函数等)。这次更新包含的修复和新功能让我非常满意。

阅读更多


实际上,自4.0版本以来,您可以在没有模型的情况下执行SProcs。您需要执行原始SQL语句而不是对象属性。即使在6.1.x版本中,您仍需使用SqlQuery<T>或ExecuteSqlCommand来获得类似的效果。 - GoldBishop

11

通过传递参数从存储过程中获取数据,这对我很有效。

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

_db 是 dbContext


9
请看这个链接,它展示了EF 6如何通过存储过程实现插入、更新和删除:http://msdn.microsoft.com/en-us/data/dn468673 附加内容 以下是一个很好的示例,展示了如何从Code First调用存储过程:
假设您需要执行一个带有单个参数的存储过程,并且该存储过程返回与实体状态匹配的数据集,那么我们将有以下操作:
var countryIso = "AR"; //Argentina

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

现在我们假设要执行另一个带有两个参数的存储过程:

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

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

请注意,我们使用基于索引的参数命名。这是因为Entity Framework将这些参数包装为DbParameter对象以避免任何SQL注入问题。
希望这个例子能帮到您!

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接