【问题标题】:EF Core LINQ use scalar functionEF Core LINQ 使用标量函数
【发布时间】:2019-01-24 14:04:50
【问题描述】:

我使用 Entity Framework Core 2.1。

我在数据库中有一个标量函数,它增加了指定的天数。 我创建了一个扩展方法来执行它:

public static class AdventureWorks2012ContextExt
    {
        public static DateTime? ExecFn_AddDayPeriod(this AdventureWorks2012Context db, DateTime dateTime, int days, string periodName)
        {
            var sql = $"set @result = dbo.[fn_AddDayPeriod]('{dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}', {days}, '{periodName}')";
            var output = new SqlParameter { ParameterName = @"result", DbType = DbType.DateTime, Size = 16, Direction = ParameterDirection.Output };
            var result = db.Database.ExecuteSqlCommand(sql, output);
            return output.Value as DateTime?;
        }
    }

我尝试在查询中使用标量函数(为了简化我使用 AdventureWorks2012 的事情),如下所示:

var persons =
    (from p in db.Person
     join pa in db.Address on p.BusinessEntityId equals pa.AddressId
     where p.ModifiedDate > db.ExecFn_AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
     select p).ToList();

但得到一个 System.InvalidOperationException: '在前一个操作完成之前在此上下文上启动了第二个操作。不保证任何实例成员都是线程安全的。'

我怎样才能做到这一点?

更新: 在伊万的回答的帮助下,我设法做到了:

var persons =
    (from p in db.Person
     join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
     join a in db.Address on bea.AddressId equals a.AddressId
     where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
     select p).ToList();

但现在我需要更新已过滤人员的 ModifiedDate。所以我这样做:

var persons =
     (from p in db.Person
      join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
      join a in db.Address on bea.AddressId equals a.AddressId
      let date = AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
      where p.ModifiedDate > date
      select new { Person = p, NewDate = date }).ToList();

  foreach (var p in persons)
      p.Person.ModifiedDate = p.NewDate ?? DateTime.Now;

  db.SaveChanges();

但得到 System.NotSupportedException: 'Specified method is not supported.'

如何在 select 语句中使用标量函数?

我尝试将查询分成两部分:

var filteredPersons =                  // ok   
   (from p in db.Person
    join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
    join a in db.Address on bea.AddressId equals a.AddressId
    where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
    select new { Person = p, a.ModifiedDate }).ToList();

var persons =                          // here an exception occurs
    (from p in filteredPersons
     select new { Person = p, NewDate = AdventureWorks2012ContextFunctions.AddDayPeriod(p.ModifiedDate, 100, "DayPeriod_day") }).ToList();

【问题讨论】:

  • 能否将fn_AddDayPeriod 修改为接受datetime 而不是varchar(或当前使用的任何文本类型)?
  • fn_AddDayPeriod 是一个相当复杂的函数。这就是我想直接调用它的原因。
  • fn_AddDayPeriod 有三个参数,分别是 DATETIME、INT 和 VARCHAR(100) 类型。我不应该修改这个函数。
  • 啊,已经是datetime,完美。我们可以修改AdventureWorks2012Context 类还是不允许的?
  • 是的,我们可以修改db上下文。

标签: linq entity-framework-core


【解决方案1】:

您可以使用 EF Core Database scalar function mapping,而不是调用客户端函数(这种特殊情况是作为查询过滤器的 client evaluation 的一部分发生的,而查询读取仍在进行中),所以它

可用于 LINQ 查询并转换为 SQL。

一种方法是在派生上下文类中创建一个公共静态方法并用DbFunction 属性标记它:

public partial class AdventureWorks2012Context
{
    [DbFunction("fn_AddDayPeriod")]
    public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
} 

并使用

where p.ModifiedDate > AdventureWorks2012Context.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")

另一种方法是在另一个类中创建一个公共静态方法

public static class AdventureWorks2012DbFunctions
{
    [DbFunction("fn_AddDayPeriod")]
    public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
} 

但是您需要使用 fluent API 注册它(这会自动发生在上下文派生类中定义的方法):

modelBuilder
    .HasDbFunction(() => AdventureWorks2012DbFunctions.AddDayPeriod(default(DateTime), default(int), default(string)));

用法同理:

where p.ModifiedDate > AdventureWorksDbFunctions.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")

【讨论】:

  • 嗨,Ivan,我更新了我的问题。你能看一下吗?
  • 嗨,通常它应该在查询的 select(和任何其他)子句中工作,所以我不确定这里发生了什么 - 看起来一些客户评估正在触及我们的 throw new NotSupportedException(); 代码。您可以通过在其上放置断点来验证吗?
  • 我已禁用客户端评估,公共静态日期时间行上仍然出现异常? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
  • 是的,我也验证了这一点。我们的实现仅在转换为 SQL 时才有效,并且在某些情况下 EF Core 会尝试在本地对其进行评估。您可以做的最好的事情是提供该方法的 C# 客户端实现,而不是抛出不受支持的异常。
  • 从 EF Core 2.1 开始,该函数可以声明为派生 DbContext 的成员函数。
猜你喜欢
  • 2020-03-29
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 2018-03-17
  • 2021-02-22
  • 1970-01-01
  • 2021-05-03
  • 2019-12-17
相关资源
最近更新 更多