Entity Framework 6-对DbFunctions的扩展方法

4

我不确定这是否已经可以实现,因为我刚开始接触Entity Framework 6。我们正在迁移到Linq2Sql,但是在我们的引擎中有许多基于用户的查询。我们根据用户要求动态编译一些代码,并必须保持向后兼容性。

一个传递自旧版Linq2Sql的动态查询示例可能如下所示(简化了一下,仅作为示例):

from item in context.SomeTableOrView 
  let sub = from sub1 in context.SomeTableOrView where 
  sub1.DataTimeStamp > item.DataTimeStamp.AddMinutes(60) &&
  sub1.DataTimeStamp < item.DataTimeStamp.AddMinutes(300) 
select posSub1 where 
sub.Take(1).Any()
select item

当然,Entity Framework没有任何形式的.AddMinutes(x)支持,您必须使用新的DbFunctions静态方法。我们不能打破代码,因此我们必须对其进行改进。首先想到的解决方案是,只需替换任何具有.AddMinutes(x).AddSeconds(x)或我们在DateTime周围执行的任何操作的文本,并用新函数替换即可完成。这是所有预编译的,所以从技术上讲应该行得通。但我不擅长正则表达式。如果有人知道如何做到这一点,我很乐意接受那作为答案。
但是,我想了解EntityFramework在扩展方法方面的工作原理。由于DateTime.AddMinutes(x)的返回值返回一个新的DateTime,因此我可以创建一个扩展方法来返回一个表达式,该表达式将执行类似于DbFunctions.AddMinutes(time, increment)这样的等效操作,或者其他创造性方法。DbFunctions是静态的,因此我不能返回Func<DbFunctions>
想法/建议。谢谢!
更新-Ivan提供了一个很好的更新答案,供任何查看此内容的人使用。比下面的答案更少出错,而且我认为相当流畅。 进入链接说明
2个回答

2
Entity Framework 6 在这种情况下有些愚蠢。它仅尝试从方法中获取关联的 [DbFunction] 属性,并使用 System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Translator私有类用 DbExpression 替换该方法。因此,没有办法从外部代码注册自定义翻译器。此外,.NET 不提供动态附加属性到语言构造的能力。
因此,要解决此问题,您可以执行以下操作之一:
  • 在源代码中将方法替换为调用 DbFunctions 类的方法(ReSharper SSR 对此很好);
  • 实现 ExpressionVisitor(和可能的 IQueryProvider),它将方法调用替换为 DbFunctions 类的方法。
  • 实现 ExpressionVisitor(和可能的 IQueryProvider),它将方法调用表达式转换为 DbFunctionExpression

1
我们发现最简单的方法是修补入站的Linq2Sql代码。 我们还注意到,DbFunctions.AddMinutes()仅接受Int32,而DateTime.AddMinutes()接受double。这可能会破坏预期的行为,因此我们也进行了修补。使用一些正则表达式和一些简单的字符串操作,得到了这个已打补丁的代码。
这可能不适用于所有人,但如果您正在从Linq2Sql转换并且有遵循Linq2Sql的保存查询,并且需要修补DateTime表达式...这将适用于AddMintues、AddDays、AddHours、AddMilliseconds。
    private static string Linq2SqlConvertToEntityFramework(string originalQuery)
    {
        // Finds expressions with .Add____)
        const string dateTimeAdditationPattern = @"(@?[a-z_A-Z]\w*(?:\.@?[a-z_A-Z]\w*)*).Add\s*.+?(?=\))\)";
        // Finds all the matches
        var matchces = Regex.Matches(originalQuery, dateTimeAdditationPattern);

        // Enumerates all the matches, and creates a patch
        foreach (Match m in matchces)
        {
            var inputValue = m.Value;

            string valuePassed = inputValue.Between("(", ")").Trim();
            string typeOfAddition = inputValue.Between(".Add", "(").Trim();
            string dateTimeExpression = inputValue.Before(".Add").Trim();

            // because DateTime.AddMinutes()  (or any other AddXXX(Y) accepts a double, and 
            // DbFunctions only accepts an int,
            // We must move this to milliseconds so we dont lose any expected behavior
            // The input value could be an int or a input variable (such as a sub query)
            var mutipler = 1;
            switch (typeOfAddition)
            {
                case "Seconds":
                    mutipler = 1000;
                    break;
                case "Minutes":
                    mutipler = 1000*60;
                    break;
                case "Hours":
                    mutipler = 1000 * 60 * 60;
                    break;
                case "Days":
                    mutipler = 1000 * 60 * 60 * 24;
                    break;
            }

            // new expression to work with Entity Framework
            var replacementString = string.Format("DbFunctions.AddMilliseconds({0},(int)({1} * {2}))", dateTimeExpression, valuePassed, mutipler);

            // Simple string replace for the input string
            originalQuery = originalQuery.Replace(inputValue, replacementString);
        }

        return originalQuery;
    }

    /// <summary>
    /// Get string value between [first] a and [last] b.
    /// Credit Source: http://www.dotnetperls.com/between-before-after
    /// </summary>
    public static string Between(this string value, string a, string b)
    {
        int posA = value.IndexOf(a, StringComparison.InvariantCulture);
        int posB = value.LastIndexOf(b, StringComparison.InvariantCulture);
        if (posA == -1)
        {
            return "";
        }
        if (posB == -1)
        {
            return "";
        }
        int adjustedPosA = posA + a.Length;
        if (adjustedPosA >= posB)
        {
            return "";
        }
        return value.Substring(adjustedPosA, posB - adjustedPosA);
    }

    /// <summary>
    /// Get string value after [first] a.
    /// Credit Source: http://www.dotnetperls.com/between-before-after
    /// </summary>
    public static string Before(this string value, string a)
    {
        int posA = value.IndexOf(a, StringComparison.InvariantCulture);
        if (posA == -1)
        {
            return "";
        }
        return value.Substring(0, posA);
    }

    /// <summary>
    /// Get string value after [last] a.
    /// Credit Source: http://www.dotnetperls.com/between-before-after
    /// </summary>
    public static string After(this string value, string a)
    {
        int posA = value.LastIndexOf(a, StringComparison.InvariantCulture);
        if (posA == -1)
        {
            return "";
        }
        int adjustedPosA = posA + a.Length;
        if (adjustedPosA >= value.Length)
        {
            return "";
        }
        return value.Substring(adjustedPosA);
    }

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