Pomelo 和 EFCore 不会忽略迁移中的视图

Pomelo & EFCore not ignoring views in migrations

我有一个使用 EFCore 和 Pomelo.EntityFrameworkCore 的 dotnet 核心项目。我刚刚从我认为的 EFCore 3.1.0 更新到 5.0.7(撰写本文时的最新版本),同时将 Pomelo 更新到 5.0.0

在我的项目中,我使用 EFCore 调用存储过程并将它们映射到视图。我已将视图添加到上下文中,如下所示:

modelBuilder.Entity<NameOfView>().HasNoKey().ToView(null);

自更新 EFCore 和 Pomelo 以来我现在注意到的是,每当我尝试 运行 迁移时,它不再忽略这些视图。根据视图是否在数据库快照中,我不断在迁移中获取 CreateTableDropTable 脚本。在更新之前,这些视图被简单地忽略了。

我是否需要为上下文中的视图设置一些 属性 以确保它们被忽略?

这似乎不是 Pomelo 或 EF Core 的问题,因为以下确实按预期工作:

  1. 创建一个包含以下内容的项目:

Project.csproj:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <Nullable>disable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.7">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.7" />
        <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.0" />
    </ItemGroup>

</Project>

Program.cs:

using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class IceCream
    {
        public int IceCreamId { get; set; }
        public string Name { get; set; }
        public DateTime BestServedBefore { get; set; }
    }

    public class ExpiredIceCream
    {
        public int IceCreamId { get; set; }
        public string Name { get; set; }
        public DateTime BestServedBefore { get; set; }
        public int DaysExpired { get; set; }
    }

    public class Context : DbContext
    {
        public DbSet<IceCream> IceCreams { get; set; }
        public DbSet<ExpiredIceCream> ExpiredIceCreams { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                var connectionString = "server=127.0.0.1;port=3306;user=root;password=;database=So67919519_01";
                var serverVersion = ServerVersion.AutoDetect(connectionString);
                optionsBuilder.UseMySql(connectionString, serverVersion)
                    .UseLoggerFactory(
                        LoggerFactory.Create(
                            configure => configure
                                .AddConsole()
                                .AddFilter(level => level >= LogLevel.Information)))
                    .EnableSensitiveDataLogging()
                    .EnableDetailedErrors();
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<IceCream>()
                .HasData(
                    new IceCream {IceCreamId = 1, Name = "Vanilla", BestServedBefore = new DateTime(2021, 1, 1)},
                    new IceCream {IceCreamId = 2, Name = "Chocolate", BestServedBefore = new DateTime(2021, 5, 15)},
                    new IceCream {IceCreamId = 3, Name = "Matcha", BestServedBefore = DateTime.Today.AddYears(5)});

            modelBuilder.Entity<ExpiredIceCream>(
                entity =>
                {
                    entity.ToView("ExpiredIceCreams");
                    entity.HasNoKey();
                });
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            var expiredIceCreams = context.ExpiredIceCreams.ToList();
            
            Trace.Assert(expiredIceCreams.Count == 2);
        }
    }
}

创建初始迁移:

dotnet ef migrations add Initial

在生成的Migrations/..._Initial.cs文件中的Up()方法末尾添加如下代码:

migrationBuilder.Sql(@"
    CREATE VIEW `ExpiredIceCreams` AS
        SELECT *, DATEDIFF(CURDATE(), `BestServedBefore`) AS DaysExpired
        FROM `IceCreams`
        WHERE `BestServedBefore` <= CURDATE();");

将以下代码添加到生成的 Migrations/..._Initial.cs 文件中 Down() 方法的开头:

migrationBuilder.Sql(@"DROP VIEW `ExpiredIceCreams`;");

更新您的数据库:

dotnet ef database update

运行 项目以确保其按预期工作。

然后添加第二个迁移:

dotnet ef migrations add Second

查看生成的 Migrations/..._Second.cs 文件。 它不应包含任何关于您的 ExpiredIceCreams 视图的操作。


从这里去哪里

我将从比较新旧 ...<MigrationName>.Design.cs 文件开始,看看发生了什么。

请随意 post 您的迁移文件内容,以便我们查看。

(也许这与某些字符集或排序规则有关 issue/change。)

您遇到了以下 EF Core 5.0 重大更改 ToView() is treated differently by migrations,特别是此处解释的升级行为后的第一次迁移

New behavior

Now ToView(string) marks the entity type as not mapped to a table in addition to mapping it to a view. This results in the first migration after upgrading to EF Core 5 to try to drop the default table for this entity type as it's not longer ignored.

我能理解更改流畅 API 行为的原因,但不能理解迁移行为(你好 EF Core,向后兼容?)。以及建议的“缓解措施”

Use the following code to mark the mapped table as excluded from migrations:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}

虽然它似乎修复了升级后的第一次迁移行为,但它需要指定假 table 名称。不幸的是,具有 ExcludeFromMigrations 功能的 ToTable 方法重载不接受 null table 名称。

我建议的解决方法似乎对现有和新的“未映射”实体都有效,即替换

.ToView(null)

.HasAnnotation(RelationalAnnotationNames.IsTableExcludedFromMigrations, true)

注意:这是 EF Core 的一般问题,而不是特定于数据库提供商的问题,因此与 MySQL 或 Pomelo 无关。