10

What is the equivalent of the following statement in EF Core?

SqlFunctions.DatePart("week", x.MyDate)

EF.Functions doesn't seem to have a DatePart method.

AndreFeijo
  • 10,044
  • 7
  • 34
  • 64
  • What `SqlFunctions.DatePart("week", x.MyDate)` returns? – Vivek Nuna Sep 27 '18 at 04:34
  • 2
    SqlFunctions doesn't exist in .NET Core. – AndreFeijo Sep 27 '18 at 04:35
  • no direct way, but you can try DateTime inputDate = DateTime.Parse(date.Trim()); CultureInfo cul = CultureInfo.CurrentCulture; int weekNum = cul.Calendar.GetWeekOfYear( inputDate, CalendarWeekRule.FirstDay, DayOfWeek.Monday); – Vivek Nuna Sep 27 '18 at 04:59
  • 2
    Week is exactly the one thing that .net's `DateTime` doesn't have. All other DateTime "part" properties, like `Year`, are translated to SQL's `DATEPART` function. But I guess that's why you ask about "week". – Gert Arnold Jun 07 '19 at 09:21

3 Answers3

8

It is possible to make use of the datepart SQL function by wrapping it with the DbFunctionAttribute. Tricky part is to tell ef core not to handle the datepart type parameter as a string. Example:

DbContext ef core <= 2.1:

public int? DatePart(string datePartArg, DateTime? date) => throw new Exception();

public void OnModelCreating(DbModelBuilder modelBuilder) {
    var methodInfo = typeof(DbContext).GetRuntimeMethod(nameof(DatePart), new[] { typeof(string), typeof(DateTime) });
    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(args => new SqlFunctionExpression(nameof(DatePart), typeof(int?), new[]
                {
                        new SqlFragmentExpression(args.ToArray()[0].ToString()),
                        args.ToArray()[1]
                }));
}

DbContext ef core >= 3.1 (static SqlFunctionExpression.Create call instead of ctor):

public int? DatePart(string datePartArg, DateTime? date) => throw new Exception();

public void OnModelCreating(DbModelBuilder modelBuilder) {
    var methodInfo = typeof(DbContext).GetRuntimeMethod(nameof(DatePart), new[] { typeof(string), typeof(DateTime) });
    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(args => SqlFunctionExpression.Create(nameof(DatePart), new[]
                {
                        new SqlFragmentExpression(args.ToArray()[0].ToString()),
                        args.ToArray()[1]
                }, typeof(int?), null));
}

Query:

repository.Where(x => dbContext.DatePart("week", x.CreatedAt) > 10);

some more info: https://github.com/aspnet/EntityFrameworkCore/issues/10404

watch out not to call the DbFunction method on an interface of DbContext. The call must happend directly on the DbContext instance.

Edit: for ef core 3.1 you can use the static method SqlFunctionExpression.Create instead of the ctor:

cyptus
  • 3,346
  • 3
  • 31
  • 52
  • I can't create the SqlFragmentExpression instance, the constructor is internal O_o –  Nov 02 '19 at 13:49
  • This has been fixed: https://github.com/aspnet/EntityFrameworkCore/pull/18208 – cyptus Nov 02 '19 at 13:51
  • oh, I see, thanks. I guess there is still no other way to do this than this, right? Is there anyone working on adding these features? –  Nov 02 '19 at 13:55
  • The new SqlFragmentExpression(args.ToArray()[0].ToString()) results in [Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression] –  Nov 02 '19 at 18:36
8

For EF Core 5, it's even different as the SqlFunctionExpression.Create factory is now obsolete and the SqlFunctionExpression construction should be used instead. Here's the updated code (if adding it to the DB Context):

public partial class ApplicationDbContext : DbContext
{
    public int? DatePart(string datePartArg, DateTimeOffset? date) => throw new InvalidOperationException($"{nameof(DatePart)} cannot be called client side.");

    protected override void OnModelCreating(ModelBuilder builder)
    {
        var methodInfo = typeof(DbFunctionExtensions).GetMethod(nameof(DatePart));

        var datePartMethodInfo = typeof(ApplicationDbContext) // Your DB Context
            .GetRuntimeMethod(nameof(ApplicationDbContext.DatePart), new[] { typeof(string), typeof(DateTimeOffset) });
        builder.HasDbFunction(datePartMethodInfo)
           .HasTranslation(args =>
                    new SqlFunctionExpression("DATEPART",
                        new[]
                        {
                            new SqlFragmentExpression((args.ToArray()[0] as SqlConstantExpression).Value.ToString()),
                            args.ToArray()[1]
                        },
                        true,
                        new[] { false, false },
                        typeof(int?),
                        null
                    )
                );
    }
}

Note: You can switch to DateTime instead of DateTimeOffset as per your needs.

(The exception has nothing to do with EF Core 5, it's just more clear for anybody working on the code later on or trying to call the method client side).

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
Ziad Akiki
  • 2,601
  • 2
  • 26
  • 41
2

for ef core 3.1 is little different solution, should cast argument first to SqlConstantExpression then pass its Value to SqlFragmentExpression constructor:

public static class DbFunctionExtensions
{
    public static int? DatePart(string type, DateTime? date) => throw new Exception();

    public static void ConfigureDbFunctions(this ModelBuilder modelBuilder)
    {
        var mi = typeof(DbFunctionExtensions).GetMethod(nameof(DatePart));

        modelBuilder.HasDbFunction(mi, b => b.HasTranslation(e =>
        {
            var ea = e.ToArray();
            var args = new[]
            {
                new SqlFragmentExpression((ea[0] as SqlConstantExpression).Value.ToString()),
                ea[1]
            };
            return SqlFunctionExpression.Create(nameof(DatePart), args, typeof(int?), null);
        }));
    }
}
Gene R
  • 3,684
  • 2
  • 17
  • 27
  • Hi @Gene-R, I am trying to get this working using EF Core 3.1. I can make the call but am getting a could not be translated error.I am calling the above with ... "DbFunctionExtensions.DatePart("w", a.ReferenceDate) ==2" in a where. Any help much appreciated – KiwiInLondon Dec 15 '20 at 18:27
  • shouldn't be `DbFunctionExtensions.DatePart("week", a.ReferenceDate)` ? Also i am checking column for NULL before using datepart – Gene R Dec 16 '20 at 12:38