簡體   English   中英

用於自定義 MySQL 函數、文字與列的實體框架核心

[英]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

在這個階段我試圖解決兩個問題:

  1. 通過任何列永遠不會影響我的 function 配置,進而影響我的翻譯。

  2. 傳遞任何列都會導致其他參數出現各種(和奇怪的)行為。 這很可能在解決#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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM