EF Core LINQ使用标量函数

7

我使用的是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: '在上次操作完成之前,在此上下文中启动了第二个操作。 任何实例成员都不能保证是线程安全的。'

我应该怎么做呢?

更新:在 Ivan 的回答的帮助下,我成功做到了。

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:“不支持指定的方法。”

我该如何在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(或其他文本类型)吗? - Ivan Stoev
fn_AddDayPeriod是一个相当复杂的函数。这就是为什么我想直接调用它的原因。 - Dmitry Stepanov
fn_AddDayPeriod有三个参数,分别为DATETIME、INT和VARCHAR(100)类型。我不应该修改这个函数。 - Dmitry Stepanov
啊,所以它已经是“datetime”了,太完美了。我们能修改 “AdventureWorks2012Context” 类吗,还是不允许? - Ivan Stoev
是的,我们可以修改数据库上下文。 - Dmitry Stepanov
1个回答

6

不要在客户端调用函数(在这种情况下,作为查询过滤器的客户端评估的一部分发生,而查询读取仍在进行中),您可以使用EF Core 数据库标量函数映射,以便在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();
} 

但是,您需要使用流利的API将其注册(对于在派生类上下文中定义的方法,这会自动发生):

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

使用方法相同:

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

嗨,伊万,我更新了我的问题。你能看一下吗? - Dmitry Stepanov
嗨,通常情况下它应该在查询的select(和任何其他)子句中工作,所以我不确定这里发生了什么-看起来像是某些客户端评估正在触发我们的' throw new NotSupportedException(); '代码。您可以通过在其上设置断点来验证吗? - Ivan Stoev
我已禁用客户端评估,但在公共静态DateTime?AddDayPeriod(DateTime dateTime, int days, string periodName)行仍会发生异常 => throw new NotSupportedException(); - Dmitry Stepanov
是的,我也验证了。我们的实现仅在翻译为SQL时起作用,在某些情况下,EF Core会尝试在本地进行评估。您最好提供该方法的C#客户端实现,而不是抛出不支持的异常。 - Ivan Stoev
从 EF Core 2.1 开始,该函数可以声明为派生 DbContext 的成员函数。 - Paul

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