使用Entity Framework 4.0/.edmx从C#调用标量函数

24
我想将我的标量函数映射到我的 .edmx 文件中,但是失败了。我右键单击实体框架映射,并选择从数据库更新模型。它出现在模型浏览器的存储过程文件夹中。
然而,当我想将它添加到模型浏览器中的 Function Imports 文件夹时,下拉列表中显示标量函数不可用的消息。有人可以帮忙吗?
我可以使用旧的方式调用标量函数,例如:
dbContext.ExecuteStoreQuery<DateTime?>(
"SELECT dbo.getMinActualLoadDate ({0}, {1}, {2}) AS MyResult", 
LoadPkid, LoadFkStartLoc, TripSheetPkid).First();

但这不是最好的方法。我的经理希望我找到一种方法,能够将标量函数放在“函数导入”文件夹中,以便我可以使用以下代码调用标量函数,而不是以前的代码:

dbContext.ExecuteFunction("getMinActualLoadDate ", paramList);

我的声望还很低,因此无法尝试添加图片来解释我的意思。不过你可以在这里找到该图片:http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/756865e5-ff25-4f5f-aad8-fed9d741c05d

谢谢。


请修改您的标题。http://meta.stackexchange.com/questions/10647/how-do-i-write-a-good-title - spajce
5个回答

17

我也遇到了同样的问题。这是我自己找到的解决方案(在EF5中测试过,但在EF4中也应该适用):

默认情况下不支持映射标量值函数,但您可以直接执行它们。

您还可以编辑edmx文件以使edmx为标量值函数生成适当的方法,但如果将模型与数据库同步,则会将其删除。

自己编写标量值函数实现:

string sqlQuery = "SELECT [dbo].[CountMeals] ({0})";
Object[] parameters = { 1 };
int activityCount = db.Database.SqlQuery<int>(sqlQuery, parameters).FirstOrDefault();

或者编辑edmx并添加Xml以对标量值函数进行自定义映射:

<Function Name="CountActivities" Aggregate="false" BuiltIn="false"    NiladicFunction="false" IsComposable="false"   ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
    <CommandText>
        SELECT [dbo].[CountActivities] (@personId)
    </CommandText>
    <Parameter Name="personId" Type="int" Mode="In" />
</Function>

这些信息可以在博客文章中找到。


8

以下是我对这个问题的解决方案,几乎与您的经理要求完全相同,尽管晚了18个月。

作为一种基本方法:

    /// <summary>
    /// Calls a given Sql function and returns a singular value
    /// </summary>
    /// <param name="db">Current DbContext instance</param>
    /// <typeparam name="T">CLR Type</typeparam>
    /// <param name="sql">Sql function</param>
    /// <param name="parameters">Sql function parameters</param>
    /// <param name="schema">Owning schema</param>
    /// <returns>Value of T</returns>
    public T SqlScalarResult<T>(DbContext db, 
                                string sql, 
                                SqlParameter[] parameters,
                                string schema = "dbo") {

        if (string.IsNullOrEmpty(sql)) {
            throw new ArgumentException("function");
        }

        if (parameters == null || parameters.Length == 0) {
            throw new ArgumentException("parameters");
        }

        if (string.IsNullOrEmpty(schema)) {
            throw new ArgumentException("schema");
        }

        string cmdText =
            $@"SELECT {schema}.{sql}({string.Join(",",
                parameters.Select(p => "@" + p.ParameterName).ToList())});";

        // ReSharper disable once CoVariantArrayConversion
        return db.Database.SqlQuery<T>(cmdText, parameters).FirstOrDefault();

    }

}

作为EF的扩展方法:

namespace System.Data.Entity {

    public static class DatabaseExtensions {

        /// <summary>
        /// Calls a given Sql function and returns a singular value
        /// </summary>
        /// <param name="db">Current DbContext instance</param>
        /// <typeparam name="T">CLR Type</typeparam>
        /// <param name="sql">Sql function</param>
        /// <param name="parameters">Sql function parameters</param>
        /// <param name="schema">Owning schema</param>
        /// <returns>Value of T</returns>
        public static T SqlScalarResult<T>(this Database db, 
                                           string sql, 
                                           SqlParameter[] parameters,
                                           string schema = "dbo") {

            if (string.IsNullOrEmpty(sql)) {
                throw new ArgumentException("sql");
            }

            if (parameters == null || parameters.Length == 0) {
                throw new ArgumentException("parameters");
            }

            if (string.IsNullOrEmpty(schema)) {
                throw new ArgumentException("schema");
            }

            string cmdText =
                $@"SELECT {schema}.{sql}({string.Join(",", 
                    parameters.Select(p => "@" + p.ParameterName).ToList())});";

            // ReSharper disable once CoVariantArrayConversion
            return db.SqlQuery<T>(cmdText, parameters).FirstOrDefault();

        }

    }

}

尽管这里不需要进行测试,但我建议在进行任何重要的使用前进行单元测试。

2
这应该被标记为已接受的答案。这种方法完美地工作,并且还利用了SqlParameters。 - BrianLegg
如果在SQL Server上不存在所请求的函数怎么办?在我看来,这段代码还不够“成熟”以用于生产环境。当然,这是一个好建议并且可以开始着手实现 :-) - Rob Angelier
1
@RobAngelier 我理解你关于生产环境“准备就绪”的观点。然而,EF团队自己的扩展方法似乎也没有处理任何远程异常:https://github.com/aspnet/EntityFramework/blob/eece19ded562ba8aa85f2173d3280a23fc4e3727/src/EntityFramework.Relational/RelationalDatabaseFacadeExtensions.cs。如果在此处捕获异常,您会期望该方法返回什么? - dperish
我遇到了这个错误:LINQ to Entities 不认识 'System.String SqlScalarResult[String](System.String, System.Data.SqlClient.SqlParameter[], System.String)' 方法,而且这个方法无法被转换成存储表达式 - Reza
我假设你是在VB.Net的更大表达式中使用它。你能否提供更多上下文信息? - dperish
显示剩余4条评论

1
唯一的解决方案是将函数标量类型转换为带有单个值的表值类型,请参见代码示例。
无需更改EDMX XML中的任何内容,请修改SQL函数。
作为标量函数,它无法工作。
CREATE FUNCTION [dbo].[GetSha256]
(
    -- Add the parameters for the function here
    @str nvarchar(max)
)
RETURNS VARBINARY(32)
AS
BEGIN
    RETURN ( SELECT * FROM HASHBYTES('SHA2_256', @str) AS HASH256 );
END -- this doesn't work.

标量函数 -> 转换为表值函数,它可以工作
CREATE FUNCTION [dbo].[GetSha2561]
(
    -- Add the parameters for the function here
    @str nvarchar(max)
)
RETURNS  @returnList TABLE (CODE varbinary(32))
AS
BEGIN

    INSERT INTO @returnList
    SELECT HASHBYTES('SHA2_256', @str);

    RETURN; -- This one works like a charm.

END

Edmx 截图

enter image description here


1
我猜你错过了编辑函数导入对话框,在那里你可以生成复杂类型。尝试探索一下。

enter image description here

如果您已成功创建了scalars,现在可以像这样导航。
using (var con = new DatabaseEntities())
{
   long? invoiceNo = con.sp_GetInvoiceMaxNumber(code.Length + 2).First();
   ....
}

3
嗨,基本上这就是我想做的事情。但是我创建的标量函数名称在存储过程名称下拉列表中并不存在(尽管我已经将标量函数添加到模型浏览器中)。如果它是一个存储过程,则会出现在DDL中,但如果是表/标量函数,则不会出现在DDL中。因此,我很难按照我在第一篇文章中提到的方式调用我的标量函数。谢谢。 - jay
EF 6.1.2仍存在问题 https://social.msdn.microsoft.com/Forums/en-US/756865e5-ff25-4f5f-aad8-fed9d741c05d/add-scalar-function-to-function-import-folder-in-model-browser-of-entity-framework-40-edmx?forum=adodotnetentityframework - Waqar

0

页面中的代码:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        using (MayEntities context = new MayEntities())
        {
            string str = context.Database.SqlQuery<string>("select dbo.HeyYou()").Single().ToString();
            Response.Write(str); //output:'Hey this works'
        }
    }
}

标量函数:

CREATE FUNCTION [dbo].[HeyYou] ()
RETURNS varchar(20)
AS
BEGIN
    RETURN 'Hey this works'
END
go

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