【问题标题】:Extend DbFunctions EF6扩展 DbFunctions EF6
【发布时间】:2017-11-27 22:52:41
【问题描述】:

假设您使用现有的 DbFunctions 作为辅助方法,是否可以扩展 DbFunctions。我本质上是在一次又一次地重写完全相同的 sql 代码行。有其他选择吗?

更新: 这是我正在尝试做的一个示例,但我想定义自己的 Add 函数,而不是使用我在数据库中构建的函数

var locations = context.Data.Where(e => Functions.Add(e.X, e.Y) >= 10)

【问题讨论】:

  • 为什么不创建一个使用 DbFunction 的公共方法并调用该方法而不是重写它?
  • 不确定我是否理解您的问题。这会有帮助吗:stackoverflow.com/a/29539227/1236044
  • 调用你自己的add函数怎么会少一些代码?为什么不只是 e => (e.X + e.Y) >= 10) ?
  • @Mant101 考虑添加只是 foo,实际函数更复杂,但这并不重要,它仍然使用在查询中可以正常工作的代码,但我想重用
  • “e”总是相同的类型,还是需要在不同的类型上工作?它总是属性 X 和 Y 吗?它需要有多灵活?

标签: entity-framework linq dbfunctions


【解决方案1】:

是的,当然你可以扩展它。 DbFunctions 类仅包含帮助 IQueryable 提供者将表达式转换为 SQL 的代码。

IEnumerable 包含所有代码,可根据您的请求为您创建 Enumerator。 Enumerator 代表一个序列。根据请求,它会给你序列的第一个元素,一旦你有了一个元素,它就会给你下一个元素(只要有一个)。

IQueryable 的工作方式不同。通常 IQueryable 并不意味着由您的进程执行,而是由数据库、远程网站、CSV 文件控制器等执行。

这就是为什么您需要告诉一个生成 IQueryable 的对象,它必须为哪个进程创建 IQueryable。在实体框架的情况下,您通知 DbContext 使用哪个数据库。

IQueryable 对象包含一个要执行的表达式和一个提供程序。提供者知道哪个进程将执行查询。它还知道如何将表达式翻译成其他进程可以理解的格式。这通常是 SQL。

如果您查看 MSDN 描述 IQueryable 函数(如 Where、GroupBy、Select)的备注部分,您会发现这些函数中的大多数只会更改表达式。

只要你不要求 Enumerator,通常是通过要求序列的第一个元素来隐式地要求,例如在 ToList、foreach、FirstOrDefault 等中,Provider 就没有任何关系。

但是,一旦您请求 Enumerator,Expression 将由 Provider 翻译,Provider 将使用翻译从其他进程中查询数据并创建一个 Enumerator 对象,该对象可以为您提供序列的第一个元素,并且下一个。

提供者将表达式转换为 SQL 时使用 DbFunctions。如果您使用 DbFunctions 创建 Queryable 并在调试器中查看创建的表达式,您仍然会找到使用的 DbFunctions。

DbFunction 仅将输入转换为 SQL。如果不执行查询本身。翻译在本地内存中完成。

了解这一点后,您可以使用任何函数,只要它只将表达式更改为您的提供者可以理解的新表达式即可。

这意味着您不能使用任何自己的函数或类。甚至有几个 LINQ 函数你不能用

supported and non-supported LINQ methods

但是,如果您的扩展函数输入一个 IQueryable 并输出一个 IQueryable,那么您的扩展函数只会更改表达式。只要您使用受支持的 LINQ 方法填充表达式就可以了

因此,如果您想使用返回仅包含到期发票的 IQueryable 的函数来扩展 IQueryable:

public static IQueryable<Invoice> WhereDueToday(this IQueryable<Invoice> invoices)
{   // returns all invoices that must be paid today
    return invoices
       .Where(invoice => DbFunctions.TruncateTime(invoice.DueDate) == DateTime.Today);
}

用法:

 IQueryable<Invoice> invoices = dbContext.Invoices
    .Where(invoice => ..);
 IQueryable<Invoice> invoicesDueToDay = invoices
     .WhereDueToday();

【讨论】:

  • 这些类型的方法的缺点是它们不能在子查询中对数据库使用,你不能在 Where 子句中使用 WhereDueToday 来表示获取项目的所有子项,因为它可以'不被翻译成SQL。如果您定义表达式,它们会更加灵活,可以涵盖db.Invoices.Where(InvoiceRules.DueToday)db.Customers.Where(c =&gt; c.Invoices.AsQueryable().Where(InvoiceRules.DueToday)) 两种场景
  • 此函数作为 IQueryable 工作,与所有其他 IQueryable 的工作方式相同:它们更改表达式。不是必须翻译成 SQL 的函数,而是必须由 Provider 翻译的 Expression。只要您不询问 IQueryable.GetEnumerator(),就不会计算表达式,您可以使用任何函数随意更改它。使用简单的 School-with-many-students 数据库自己尝试一下
  • 你写的工作,但是返回表达式更好,因为它更灵活,我曾经写过这样的扩展方法,但遇到了它们的局限性。如果我想要所有今天有发票的客户,我不能使用该扩展方法编写db.Customers.Where(c =&gt; c.Invoices.AsQueryable().WhereDueToday().Any()),它将尝试将“WhereDueToday”翻译成商店表达式并给出“不识别方法”错误。如果我定义一个表达式,我可以在子查询中使用它并写db.Customer.Where(c =&gt; c.Invoices.AsQueryable().Any(InvoiceRule.DueToday))
【解决方案2】:

您可以定义一个返回表达式的方法并在 where 子句中使用它。既然要传递对象的不同属性,就不能只写一个表达式

public Expression<Func<T, bool>> MyFunc<T>(Expression<T, int> property1, Expression<T, int> property2, int greaterThan)
{
    // Build expression tree
}

我意识到“构建表达式树”并不是很有用,但如果您真的不想添加,那么写出构建“添加”的代码也无济于事。

如果只有几个组合,那么为这些组合硬编码可能会更容易

public Expression<Func<T, bool>> MyFunc<T>(PropertiesEnum p, int greaterThan)
{
     switch(p)
     {
          case (p.XandY):
              return item => (item.X + item.Y) > greaterThan;
          case (p.XandZ):
              return item => (item.X + item.Z) > greaterThan;
          case (p.YandZ):
              return item => (item.X + item.Z) > greaterThan;
          // other cases
     }
}

你可以这样称呼:

var locations = context.Data.Where(MyFunc(PropertiesEnum.XandY, 10));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多