-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to write DbFunction's translation? #11295
Comments
@smitpatel will provide some guidance here. |
@smitpatel Also, if this can be done in a way that we get docs out of it then all the better! /cc @divega |
@Kation
So in your function definition, all you have to configure is its name & schema in database. public static class JsonExtensions
{
public static string JsonValue(string column, [NotParameterized] string path)
{
throw new NotSupportedException();
}
} And in your OnModelCreating modelBuilder.HasDbFunction(typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.JsonValue)))
.HasName("JSON_VALUE") // function name in server
.HasSchema(""); // empty string since in built functions has no schema Starting from 3.0, using followign in protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure model
modelBuilder.HasDbFunction(typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.JsonValue)))
.HasTranslation(e => SqlFunctionExpression.Create(
"JSON_VALUE", e, typeof(string), null));
} With above code added, for query like where t.Log is column containing JSON var path = "$.Filter"; //this argument used to be dynamic
var result = db.Blogs.Select(t => JsonExtensions.JsonValue(t.Log, path)).ToArray(); generates SQL which works correctly for me in SqlServer 2016 SELECT JSON_VALUE([t].[Log], N'$.Filter')
FROM [Blogs] AS [t] Let me know if you still face issues with translation. |
@smitpatel Just an addition to your comment above. In case someone uses a builder.Property(x => x.Log).HasConversion(new ValueConverter<object, string>(
v => v != null ? JsonConvert.SerializeObject(v, serializerSettings) : null,
v => v != null ? JsonConvert.DeserializeObject<object>(v) : null)); then the on the public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public object Log { get; set; }
} In this case the query will continue to translate to valid SQL if we cast the object back to string var path = "$.Filter"; //this argument used to be dynamic
var result = db.Blogs.Select(t => JsonExtensions.JsonValue((string)t.Log, path)).ToArray(); By the way I tried this on a where clause and it still works! |
For anyone looking around here in the future, be cautious using the https://docs.microsoft.com/en-us/sql/t-sql/functions/json-value-transact-sql?view=sql-server-2017
|
the program is dead after throw new NotSupportedException() |
EF Core by default uses decimal(18, 2) for Convert.ToDecimal. I need to use different precision and scale for my conversion. For this, I have defined a DB function as below and also register it during model creation. [DbFunction("CONVERT", Schema = "")]
public static decimal Convert([NotParameterized]string dataType, double value)
{
throw new NotImplementedException();
} I am calling the above function in my query. where DbFunctions.Convert("DECIMAL(31, 6)", sale.Discount/ 100) > 0 Here Amount is a double value (The database is designed 20 years back, so I can't change the datatype) SQL generated is WHERE CONVERT(N'DECIMAL(31, 6)', sale.Discount / 100) > 0, Note: The quotes are generated around DECIMAL, instead of a SQL literal. Any ideas? |
public static class DbFunctions { [DbFunction("JSON_VALUE", "")] public static string JsonValue(string column, [NotParameterized] string path) { throw new NotSupportedException(); } }when program run to "throw new NotSupportedException()" then die
…------------------ 原始邮件 ------------------
发件人: "Smoky"<notifications@github.com>;
发送时间: 2019年7月15日(星期一) 下午2:36
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Comment"<comment@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
EF Core by default uses decimal(18, 2) for Convert.ToDecimal. I need to use different precision and scale for my conversion. For this, I have defined a DB function as below and also register it during model creation.
[DbFunction("CONVERT", Schema = "")] public static decimal Convert([NotParameterized]string dataType, double value) { throw new NotImplementedException(); }
I am calling the above function in my query.
where DbFunctions.Convert("DECIMAL(31, 6)", sale.Discount/ 100) > 0
Here Amount is a double value (The database is designed 20 years back, so I can't change the datatype)
SQL generated is
WHERE CONVERT(N'DECIMAL(31, 6)', sale.Discount / 100) > 0,
Note: The quotes are generated around DECIMAL, instead of a SQL literal.
Any ideas?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
what's happened?
…------------------ 原始邮件 ------------------
发件人: "Smoky"<notifications@github.com>;
发送时间: 2019年7月15日(星期一) 下午2:36
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Comment"<comment@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
EF Core by default uses decimal(18, 2) for Convert.ToDecimal. I need to use different precision and scale for my conversion. For this, I have defined a DB function as below and also register it during model creation.
[DbFunction("CONVERT", Schema = "")] public static decimal Convert([NotParameterized]string dataType, double value) { throw new NotImplementedException(); }
I am calling the above function in my query.
where DbFunctions.Convert("DECIMAL(31, 6)", sale.Discount/ 100) > 0
Here Amount is a double value (The database is designed 20 years back, so I can't change the datatype)
SQL generated is
WHERE CONVERT(N'DECIMAL(31, 6)', sale.Discount / 100) > 0,
Note: The quotes are generated around DECIMAL, instead of a SQL literal.
Any ideas?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
have you registered the DbFunction? @yechao59228866 register DbFunction like follows in DbContext: public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<TestEntity> TestEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(() => DbFunctions.JsonValue(default, default));
}
} you can have a look at my example |
@WeihanLi , it is using decorator in its function, there is no need to map this using HasDbFunction, this is done internally. What happens is that the string in EFCore is treated as UNICODE, but we can change this behavior. @wsaeed , try this! public class SampleContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer("Server=.,1433;Database=SampleFunctions;Integrated Security=True;");
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder
.HasDbFunction(typeof(SampleContext)
.GetMethod(nameof(SampleContext.Convert)))
.HasTranslation(args =>
{
var arguments = args.ToList();
arguments[0] = new SqlFragmentExpression((string)((ConstantExpression)argumentos[0]).Value);
return new SqlFunctionExpression(
"CONVERT",
typeof(decimal),
arguments);
});
}
public static decimal Convert([NotParameterized]string dataType, double value)
{
throw new NotImplementedException();
}
} |
I use this demo,but also appear System.NotSupportedException:“Specified method is not supported.”
…------------------ 原始邮件 ------------------
发件人: "liweihan"<notifications@github.com>;
发送时间: 2019年7月15日(星期一) 下午5:35
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Mention"<mention@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
have you registered the DbFunction? @yechao59228866 , you can have a look at my example
https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/dev/samples/WeihanLi.EntityFramework.Samples/Program.cs
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@yechao59228866, using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Sample
{
class Program
{
static void Main(string[] args)
{
using (var db = new SampleContext())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Set<Issue11295>().Add(new Issue11295
{
Description = "Sample",
Amount = 19.69d
});
db.SaveChanges();
var data = db
.Issue11295
.Select(p => SampleContext.Convert("DECIMAL(31, 6)", p.Amount / 100));
foreach (var item in data)
{
Console.WriteLine($"Value: {item}");
}
}
Console.ReadKey();
}
}
public class SampleContext : DbContext
{
private static readonly ILoggerFactory _loggerFactory = new LoggerFactory()
.AddConsole((s, l) => l == LogLevel.Information && s.EndsWith("Command"));
public DbSet<Issue11295> Issue11295 { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder
.UseLoggerFactory(_loggerFactory)
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ExtensionSample;Integrated Security=True;");
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder
.HasDbFunction(typeof(SampleContext)
.GetMethod(nameof(SampleContext.Convert)))
.HasTranslation(args =>
{
var arguments = args.ToList();
arguments[0] = new SqlFragmentExpression((string)((ConstantExpression)arguments[0]).Value);
return new SqlFunctionExpression(
"CONVERT",
typeof(decimal),
arguments);
});
}
public static decimal Convert(string dataType, double value)
=> throw new NotImplementedException();
}
public class Issue11295
{
public int Id { get; set; }
public string Description { get; set; }
public double Amount { get; set; }
}
} Output info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (32ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [Issue11295] (
[Id] int NOT NULL IDENTITY,
[Description] nvarchar(max) NULL,
[Amount] float NOT NULL,
CONSTRAINT [PK_Issue11295] PRIMARY KEY ([Id])
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (86ms) [Parameters=[@p0='?' (DbType = Double), @p1='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Issue11295] ([Amount], [Description])
VALUES (@p0, @p1);
SELECT [Id]
FROM [Issue11295]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT CONVERT(DECIMAL(31, 6), [p].[Amount] / 100E0)
FROM [Issue11295] AS [p]
Value: 0.196900 |
so our's vs setting is different?when the program run to the "throw new NotSupportedException()" ,then program can't running Continue ,i don't know what's happened
…------------------ 原始邮件 ------------------
发件人: "Rafael Almeida"<notifications@github.com>;
发送时间: 2019年7月18日(星期四) 凌晨3:07
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Mention"<mention@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
@yechao59228866,
This works perfect for me!
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query.Expressions; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Linq.Expressions; namespace Sample { class Program { static void Main(string[] args) { using (var db = new SampleContext()) { db.Database.EnsureDeleted(); db.Database.EnsureCreated(); db.Set<Issue11295>().Add(new Issue11295 { Description = "Sample", Amount = 19.69d }); db.SaveChanges(); var data = db .Issue11295 .Select(p => SampleContext.Convert("DECIMAL(31, 6)", p.Amount / 100)); foreach (var item in data) { Console.WriteLine($"Value: {item}"); } } Console.ReadKey(); } } public class SampleContext : DbContext { private static readonly ILoggerFactory _loggerFactory = new LoggerFactory() .AddConsole((s, l) => l == LogLevel.Information && s.EndsWith("Command")); public DbSet<Issue11295> Issue11295 { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder .UseLoggerFactory(_loggerFactory) .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ExtensionSample;Integrated Security=True;"); } protected override void OnModelCreating(ModelBuilder builder) { builder .HasDbFunction(typeof(SampleContext) .GetMethod(nameof(SampleContext.Convert))) .HasTranslation(args => { var arguments = args.ToList(); arguments[0] = new SqlFragmentExpression((string)((ConstantExpression)arguments[0]).Value); return new SqlFunctionExpression( "CONVERT", typeof(decimal), arguments); }); } public static decimal Convert(string dataType, double value) => throw new NotImplementedException(); } public class Issue11295 { public int Id { get; set; } public string Description { get; set; } public double Amount { get; set; } } }
Output
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (32ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE [Issue11295] ( [Id] int NOT NULL IDENTITY, [Description] nvarchar(max) NULL, [Amount] float NOT NULL, CONSTRAINT [PK_Issue11295] PRIMARY KEY ([Id]) ); info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (86ms) [Parameters=[@p0='?' (DbType = Double), @p1='?' (Size = 4000)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; INSERT INTO [Issue11295] ([Amount], [Description]) VALUES (@p0, @p1); SELECT [Id] FROM [Issue11295] WHERE @@rowcount = 1 AND [Id] = scope_identity(); info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT CONVERT(DECIMAL(31, 6), [p].[Amount] / 100E0) FROM [Issue11295] AS [p] Value: 0.196900
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Post a repro project. |
I see the problem ,because i'm not reg methods on the dbcontext
modelBuilder.HasDbFunction(() => MyDbFunctions.JsonValue(default(string), default(string)));
thank you!
…------------------ 原始邮件 ------------------
发件人: "Rafael Almeida"<notifications@github.com>;
发送时间: 2019年7月18日(星期四) 上午9:36
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Mention"<mention@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
Post a repro project.
Maybe someone can help you, without this it is difficult to understand, what I posted is just a way to get around what you need for the moment.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@ralmsdeveloper it seemed you had to register the |
Microsoft.EntityFrameworkCore.SqlServer 2.2.4 in my program,It can't auto register.
…------------------ 原始邮件 ------------------
发件人: "liweihan"<notifications@github.com>;
发送时间: 2019年7月18日(星期四) 上午10:37
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Mention"<mention@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
@ralmsdeveloper it seemed you had to register the DbFunction explicitly, it will not auto register (as my test, tested via Microsoft.EntityFrameworkCore.SqlServer 2.2.0 and 2.2.6). tested project here https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/dev/samples/WeihanLi.EntityFramework.Samples/Program.cs
Supported auto register from 3.0?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
I wanted to say that when using a method that has a [DbFunction] attribute there is no need to map in ModelBuild. [DbFunction("CONVERT","")]
public static decimal Convert(string dataType, double value)
=> throw new NotImplementedException(); protected override void OnModelCreating(ModelBuilder builder)
{
//This is no longer necessary.
//builder.HasDbFunction(() => SampleContext.Convert(default, default));
} |
@ralmsdeveloper , which EFCore version you're using? I defined the dbFunction as follows: [DbFunction("JSON_VALUE", "")]
public static string JsonValue(string column, [NotParameterized] string path)
{
throw new NotSupportedException();
} but I still had to register the DbFunction in the DbContext like follows: public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<TestEntity> TestEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(() => DbFunctions.JsonValue(default, default));
}
} |
Your method is outside of your DbContext, which is why you do not register automatically. |
@ralmsdeveloper thanks for your info, that's really a bad news, cause I wanna put the custom dbFunctions into a common library |
yes,my method is in other class ,so it's must register in modelBuilder
thank for you help
…------------------ 原始邮件 ------------------
发件人: "Rafael Almeida"<notifications@github.com>;
发送时间: 2019年7月18日(星期四) 中午12:51
收件人: "aspnet/EntityFrameworkCore"<EntityFrameworkCore@noreply.github.com>;
抄送: "59228866"<59228866@qq.com>; "Mention"<mention@noreply.github.com>;
主题: Re: [aspnet/EntityFrameworkCore] How to write DbFunction'stranslation? (#11295)
Your method is outside of your DbContext, which is why you do not register automatically.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@ralmsdeveloper @yechao59228866 @WeihanLi this is not StackOverflow or Gitter, so please stop flooding this issue with your comments. @ajcvickers please remove all non-relevant comments, including mine, thank you. |
Updated #11295 (comment) for 3.0 breaking changes. |
Does anyone knows how to generate CONVERT(VARCHAR, 10)? I'm trying: return SqlFunctionExpression.Create("CONVERT", new[] { new SqlConstantExpression(Expression.Constant("VARCHAR"), new StringTypeMapping("VARCHAR")), e.First() }, typeof(string), new StringTypeMapping("varchar")); But it generates: CONVERT('VARCHAR', 10); |
return SqlFunctionExpression.Create("CONVERT", new[] { new SqlFragmentExpression("VARCHAR(max)"), e.First() }, typeof(string), new SqlServerStringTypeMapping()); |
On 3.0 SqlFragmentExpression has no public constructor. |
@brunosantosrhsenso this has been fixed in 3.1 which is days away from being released. In the meantime you can try out preview3 which is available on nuget.org. |
thanks a lot, this comment help me a lot, because i was my prototype funcitonality on LinqPad, working like a charm, when i put that on my project, cannot translate DbFunction, and try the WeinhanLi example without success, i think something is missing there. where is JsonValue method? |
I have been writing my JSON_VALUE like this, which I found is the only way that works:
But now |
@rogerfar Although your code works, SqlFunctionExpression.Create() has been marked as obsolete in .NET 5.0. It says use new SqlFunctionExpression() with appropriate arguments but I can't get it to work. The last constructor argument of type RelationalTypeMapping is confusing and I don't know what to do with it. Any ideas? |
|
new SqlFunctionExpression(
"JSON_VALUE",
args,
nullable: true,
argumentsPropagateNullability: new [] { false, false },
typeof(string),
null) |
Thanks @smitpatel this works for me. |
@smitpatel Ah! I see. It was argumentsPropagateNullability that I was missing. Excellent! Thank you very much, works. |
@smitpatel I have tried your implementation as following:
Instead of
But it's not giving the same results. When I run this query:
I get this result at the first option:
While I get this with the 2nd option:
|
@rogerfar - Updated my comment. A slightly incorrect argument passed optimizes SQL that way. Essentially if column is null then value of function is going to be null but other way is not true (if function value is null then column must have been null). |
@smitpatel Ah I see, the argumentsPropagateNullability now has { false, false }. Gotta test it in production but thank you. |
Thanks a ton! Had to use this exact approach in EF Core 9 to get it working after migrating from EF Core 3.1. I do hope EF Core will ship with these functions out of the box, they are so useful and helpful, and I have to presume such a very common scenario; perhaps coming to a milestone in the future then. |
When we use ModelBuilder.HasDbFunction to add a database function to model
It generate the SQL with QUOTE char all of the arguments
With SQL Server 2016, there is a JSON_VALUE function
It's require first argument to be column without QUOTE, and second argument not be a parameter.
I wrote a test to solve first question but a bug exist.
Write a static method
Write a expression translate method
Write a json expression
Configure model
Write query
It came out sql with
JSON_VALUE([Values],@__path_1)
And column name has no table relate, path is a parameter
-- How to generate sql without QUOTE?
-- How to generate sql that argument is not a PARAMETER?
The text was updated successfully, but these errors were encountered: