【问题标题】:How can I extract DbFunctions/SqlFunctions call into a resuable extension/method with EntityFramework 6.2?如何使用 EntityFramework 6.2 将 DbFunctions/Sql 函数调用提取到可重用的扩展/方法中?
【发布时间】:2018-12-26 20:31:46
【问题描述】:

我在 ASP.NET MVC 5 框架的顶部使用C# 编写了一个应用程序。此外,我使用 EntityFramework 6.2 作为 ORM 来与我的数据进行交互。

我使用 Fluent LINQ 编写了以下连接语句

List<string> names = new List<string>{"", ....};
query = TopLevelQuery.Where(x => x.EndedAt.HasValue && x.StartedAt >= startedAt && x.EndedAt.Value <= endedAt)
                     .Join(UserService.QueryUniqueRecords(),
                     entry => entry.UserId,
                     rec => rec.UserId,
                     (entry, rec) => new { entry, rec })
                     .Where(result => result.entry.IsEqualDateOf(result.rec.DayOf) 
                         && names.Contains(result.rec.Name))
                     .Select(x => x.entry);

但是,我在运行时收到以下错误

LINQ to Entities 无法识别方法“IsEqualDateOf”,并且 此方法无法转换为商店表达式。

这是我的IsEqualDateOf扩展方法

public static class MyModelExtensions
{
    public static DateTime GetLocalDate(this MyModel entry)
    {
        var local = DbFunctions.TruncateTime(SqlFunctions.DateAdd("ss", entry.UtcOffset, entry.StartedAt));

        return local.Value;
    }

    public static bool IsEqualDateOf(this MyModel entry, DateTime dateOf)
    {
        bool isEqual = entry.GetLocalDate().Equals(dateOf.Date);

        return isEqual;
    }
}

但是,如果我将 LINQ 表达式转换为以下伪表达式,它会按预期工作

query = TopLevelQuery.Where(x => x.EndedAt.HasValue && x.StartedAt >= startedAt && x.EndedAt.Value <= endedAt)
                     .Join(UserService.QueryUniqueRecords(),
                     entry => entry.UserId,
                     rec => rec.UserId,
                     (entry, rec) => new { entry, rec })
                     .Where(result => DbFunctions.TruncateTime(SqlFunctions.DateAdd("ss", entry.UtcOffset, entry.StartedAt)) == result.rec.DayOf 
                         && names.Contains(result.rec.Name))
                     .Select(x => x.entry);

但是,我希望能够在我的项目中的多个地方重用相同的逻辑,这就是为什么我想将它提取到某种扩展或方法中。

如何,我可以将DbFunctionsSqlFunctions 调用提取到一个可重用的方法中,在调用AsEnumerable() 之前可以在LINQ 中使用该方法?

更新

我还尝试通过将以下代码添加到 MyModel 类来将逻辑提取到 lambda 表达式中

public class MyModel
{
    public DateTime StartedAt { get; set; }
    public int UtcOffset { get; set; }
    // ...

    public Expression<Func<MyModel, bool>> IsDateOf(DateTime dayOf)
    {
        return p => p.StartedAt.AddSeconds(p.UtcOffset) == dayOf.Date;
    }
}

然后我试着像这样消费它

query = TopLevelQuery.Where(x => x.EndedAt.HasValue && x.StartedAt >= startedAt && x.EndedAt.Value <= endedAt)
                     .Join(UserService.QueryUniqueRecords(),
                     entry => entry.UserId,
                     rec => rec.UserId,
                     (entry, rec) => new { entry, rec })
                     .Where(result => result.entry.IsDateOf(result.rec.DayOf) 
                         && names.Contains(result.rec.Name))
                     .Select(x => x.entry);

但是在尝试使用它时会引发以下语法错误

运算符 '&&' 不能应用于类型的操作数 'Expression&lt;Func&lt;MyModel,bool&gt;&gt;' 和布尔值。

此外,我尝试将IsDateOf 表达式设为静态并这样称呼它

query = TopLevelQuery.Where(x => x.EndedAt.HasValue && x.StartedAt >= startedAt && x.EndedAt.Value <= endedAt)
                     .Join(UserService.QueryUniqueRecords(),
                     entry => entry.UserId,
                     rec => rec.UserId,
                     (entry, rec) => new { entry, rec })
                     .Where(result => result.entry.Where(MyModel.IsDateOf(result.rec.DayOf))
                         && names.Contains(result.rec.Name))
                     .Select(x => x.entry);

但这给了我以下语法错误

'MyMode' 不包含 Where 的定义和最好的 扩展方法重载 Queryable.Where<MyModel>IQueryable<MyModel>, Expression<Func<MyModel, bool>>) 需要一个 IQueryable 类型的接收器

【问题讨论】:

  • AFAIK 你必须把它变成一个 lambda 表达式而不是一个方法,比如Expression&lt;Func&lt;Datetime, bool&gt;&gt;,它不能有一个主体。有关我的意思的示例,请参见 this question
  • @Amy 我实际上已经尝试过了。请检查我更新的问题。
  • 您正在调用一个返回表达式的方法。您没有调用表达式。
  • 你现在为什么要打电话给result.entry.Where?摆脱它。
  • 由于某种原因,您现在拥有 .Where(result =&gt; result.entry.Where(MyModel.IsDateOf(result.rec.DayOf)) &amp;&amp; names.Contains(result.rec.Name)) 将其更改为 .Where(result =&gt; MyModel.IsDateOf(result.entry, result.rec.DayOf) &amp;&amp; names.Contains(result.rec.Name))

标签: c# asp.net-mvc entity-framework linq entity-framework-6


【解决方案1】:

从你的方法中移除参数并让表达式接受两个参数:

public static Expression<Func<MyModel, DateTime, bool>> IsDateOf 
    = (MyModel p, DateTime d) => p.StartedAt.AddSeconds(p.UtcOffset) == d.Date;

请注意,我已将其设为静态字段,而不是方法。然后在您的 LINQ 表达式中,您需要调用它:

MyModel.IsDateOf(result.entry, result.rec.DayOf)

如果不将其设为字段(或属性),则必须先调用方法来获取表达式;那么你需要调用表达式:

MyModel.IsDateOf()(result.entry, result.rec.DayOf)

在我看来,这看起来很奇怪。

【讨论】:

  • 谢谢!调用MyModel.IsDateOf(result.entry, result.rec.DayOf) 时出现以下错误Non-invocable member 'MyModel.IsDateOf' cannot be used like a method. 这是我如何称呼它(entry, rec) =&gt; new { entry, rec}).Where(result =&gt; MyModel.IsDateOf(result.entry, result.rec.DayOf) &amp;&amp; names.Contains(result.rec.Name)). 不确定这次我错过了什么。
  • 您能告诉我您正在使用的Where 的重载是将Expression&lt;&gt; 对象作为参数还是实际的Func&lt;&gt; 对象?
  • 认为 @Amy 的回答是假设您使用的 Where 重载的参数为 Expression&lt;Func&lt;MyModel,bool&gt;&gt;,但调用中的其他内容正在强制执行它使用采用普通Func&lt;MyModel, bool&gt; 的重载。这会导致它反对:它想要一个方法,一个Func&lt;&gt;,它被赋予了一个Expression&lt;Func&lt;&gt;&gt;
  • 据我所知,.Where(result =&gt; ) 接受 IQueryable&lt;'a&gt; IQueryable&lt;'a&gt;.Where&lt;'a&gt;(Expression&lt;Func&lt;a', bool&gt;&gt; predicts) a' 是新的 { MyModel entry, MyRecord rec}
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2016-08-26
  • 1970-01-01
  • 1970-01-01
  • 2012-03-31
相关资源
最近更新 更多