[英]Entity Framework Core for custom MySQL Functions, Literals vs Columns
我正在嘗試在 EF Core 3.1、.NET Core 3.1 中使用 map a MySQL function。 更具體地說,我正在嘗試 map params object[]
類型的參數。
下面的示例代碼使用CONCAT()
進行說明。
在擺弄HasTranslation()
和自定義RelationalTypeMapping
之后,它可以處理文字。 這意味着它會生成正確的 SQL 查詢並將成功執行該查詢。
一旦我傳遞一個列,它就無法生成正確的 SQL 查詢,並且會在執行時拋出NotSupportedException
。
在這個階段我試圖解決兩個問題:
通過任何列永遠不會影響我的 function 配置,進而影響我的翻譯。
傳遞任何列都會導致其他參數出現各種(和奇怪的)行為。 這很可能在解決#1 后很容易解決,但這很有趣,n.netheless。 (即"asdf"
與(object)"asdf"
)
用於調試的ToSql()
擴展方法來自https://stackoverflow.com/a/67901042/1048799
代碼中注釋了 output。
套餐
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="3.1.17" />
測試服務.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace EfTest
{
public interface IEfTestService
{
Task RunAsync();
}
public class EfTestService : IEfTestService
{
private readonly ILogger<IEfTestService> logger;
private readonly EfTestDbContext dbContext;
public EfTestService(ILogger<IEfTestService> logger, EfTestDbContext dbContext)
{
this.logger = logger;
this.dbContext = dbContext;
}
public async Task RunAsync()
{
var concat0 = dbContext.TestRecords.Select(r => dbContext.Concat(0, 1, "2", 3.0d, 4.0m, "five"));
var concat0Sql = concat0.ToSql();
//var concat0Result = await concat0.FirstAsync();
Console.WriteLine(concat0Sql);
/*
* works as expected
*
SELECT `CONCAT`(0, 1, '2', 3, 4.0, 'five')
FROM `TestRecords` AS `t`
*/
var concat1 = dbContext.TestRecords.Select(r => dbContext.Concat(r.TestRecordId));
var concat1Sql = concat1.ToSql();
//var concat1Result = await concat1.FirstAsync();
Console.WriteLine(concat1Sql);
/*
* not CONCAT
*
SELECT `t`.`TestRecordId`
FROM `TestRecords` AS `t`
*/
var concat2 = dbContext.TestRecords.Select(r => dbContext.Concat(r.TestRecordId, 0.1));
var concat2Sql = concat2.ToSql();
//var concat2Result = await concat2.FirstAsync();
Console.WriteLine(concat2Sql);
/*
* not CONCAT, 0.1 is included
*
SELECT `t`.`TestRecordId`, 0.1
FROM `TestRecords` AS `t`
*/
var concat3 = dbContext.TestRecords.Select(r => dbContext.Concat(r.TestRecordId, "asdf"));
var concat3Sql = concat3.ToSql();
//var concat3Result = await concat3.FirstAsync();
Console.WriteLine(concat3Sql);
/*
* not CONCAT, asdf is NOT included
*
SELECT `t`.`TestRecordId`
FROM `TestRecords` AS `t`
*/
var concat4 = dbContext.TestRecords.Select(r => dbContext.Concat(r.TestRecordId, (object)"asdf"));
var concat4Sql = concat4.ToSql();
//var concat4Result = await concat4.FirstAsync();
Console.WriteLine(concat4Sql);
/*
* not CONCAT, asdf is included
*
SELECT `t`.`TestRecordId`, 'asdf'
FROM `TestRecords` AS `t`
*/
}
}
}
EfTestDbContext.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace EfTest
{
public class EfTestDbContext : DbContext
{
public EfTestDbContext(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder
.HasDbFunction(() => Concat(default))
.HasTranslation(expressions =>
{ /* breakpoint, hit when passing only literals */
if (expressions.First() is SqlConstantExpression expression)
{
if (expression.Value is object[] @params)
{
var args = @params.Select(p => new SqlConstantExpression(Expression.Constant(p), ObjectTypeMapping.Instance));
return SqlFunctionExpression.Create("`CONCAT`", args, typeof(string), null);
}
}
throw new InvalidOperationException();
})
.HasParameter("vals").Metadata.TypeMapping = RelationalTypeMapping.NullMapping;
}
[DbFunction("CONCAT")]
public string Concat(params object[] vals)
=> throw new NotSupportedException(); /* thown at execution when passing a column */
public DbSet<TestRecord> TestRecords { get; set; }
}
public class TestRecord
{
public int TestRecordId { get; set; }
}
}
ObjectTypeMapping.cs
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Text.RegularExpressions;
namespace EfTest
{
public class ObjectTypeMapping : RelationalTypeMapping
{
public static readonly ObjectTypeMapping Instance = new ObjectTypeMapping();
public ObjectTypeMapping() : base("object", typeof(object), System.Data.DbType.Object, true) { }
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> throw new NotImplementedException();
public override string GenerateSqlLiteral(object value)
{
if (value is string strValue)
{
strValue = Regex.Replace(strValue, @"([\\])", @"\$1");
if (!strValue.Contains("'"))
{
return $"'{strValue}'";
}
else if (!strValue.Contains('"'))
{
return $"\"{strValue}\"";
}
else
{
strValue = Regex.Replace(strValue, "(['\"])", @"\$1");
return $"'{strValue}'";
}
}
return base.GenerateSqlLiteral(value);
}
}
}
程序.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
namespace EfTest
{
class Program
{
static async Task Main(string[] args)
{
IHost host = null;
try
{
host = CreateHostBuilder(null).Build();
var efTestService = ActivatorUtilities.GetServiceOrCreateInstance<IEfTestService>(host.Services);
await host.StartAsync();
await efTestService.RunAsync();
await host.WaitForShutdownAsync();
}
finally
{
host?.Dispose();
}
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddDbContext<EfTestDbContext>(
options => options.UseMySQL(
context.Configuration.GetConnectionString("DefaultConnection")
)
);
services.AddSingleton<IEfTestService, EfTestService>();
});
return builder;
}
}
}
通過任何列永遠不會影響我的 function 配置,進而影響我的翻譯。
不可能將 map params object[]
直接傳遞給任何 EF class。
每個參數都必須明確定義類型和計數。
在映射之前展開輸入的變體並轉換為適當的 EF 結構/類。
是的,很多數據和映射類。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.