web-dev-qa-db-ja.com

コードファーストマイグレーションと初期化エラー

コードファースト移行機能の使用方法がわかりません。私の理解では、データベースがまだ存在しない場合はデータベースを作成し、移行ファイルに従って最新のスキーマに更新する必要があります。しかし、私はいつも多くのエラーが発生し、これを適切に使用する方法が全体的にわからないため、苦労しています。

_internal class Program
{
    private static void Main()
    {
        EntityFrameworkProfiler.Initialize();

        Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Migrations.Configuration>());

        using (var context = new MyContext())
        {
            var exists = context.Database.Exists();
            if (!exists)
            {
                context.Database.Create();
            }

            var element = context.Dummies.FirstOrDefault();
        }
    }
}

public class MyContext : DbContext
{
    public MyContext()
        : base(string.Format(@"DataSource=""{0}""", @"C:\Users\user\Desktop\MyContext.sdf"))
    {
    }

    public DbSet<Dummy> Dummies { get; set; }
}

internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
    }

    protected override void Seed(CodeFirstTest.MyContext context)
    {
    }
}
_

Entity Framework Profilerを使用して、実行されるステートメントを確認します。データベースが存在しない状態でプログラムを実行すると、次の出力が得られます。

-ステートメント#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#2警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory] ​​at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior Behavior、String method、ResultSetOptions options)at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior Behavior)at HibernatingRhinos.Profiler.Appender.ProfiledData ProfiledCommand.ExecuteDbDataReader(CommandBehaviorの動作)

-ステートメント#3 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#4警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory] ​​at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior Behavior、String method、ResultSetOptions options)at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior Behavior)at HibernatingRhinos.Profiler.Appender.ProfiledData ProfiledCommand.ExecuteDbDataReader(CommandBehaviorの動作)

-ステートメント#5 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#6警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory] ​​at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior Behavior、String method、ResultSetOptions options)at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior Behavior)at HibernatingRhinos.Profiler.Appender.ProfiledData ProfiledCommand.ExecuteDbDataReader(CommandBehaviorの動作)

-ステートメント#7 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#8警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory] ​​at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior Behavior、String method、ResultSetOptions options)at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior Behavior)at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior Behavior)at HibernatingRhinos.Profiler.Appender.ProfiledData ProfiledCommand.ExecuteDbDataReader(CommandBehaviorの動作)

-ステートメント#9は分離レベルでトランザクションを開始します:Serializable

-ステートメント#10 CREATE TABLE [Dummies]([Name] nvarchar NOT NULL、CONSTRAINT [PK_Dummies] PRIMARY KEY([Name]))

-ステートメント#11 CREATE TABLE [MigrationHistory]([MigrationId] nvarchar NOT NULL、[CreatedOn] [datetime] NOT NULL、[Model] [image] NOT NULL、[ProductVersion] nvarchar NOT NULL、CONSTRAINT [PK _ MigrationHistory] ​​PRIMARY KEY([MigrationId]))

-ステートメント#12 INSERT INTO [__MigrationHistory]([MigrationId]、[CreatedOn]、[Model]、[ProductVersion])VALUES( '201207261524579_InitialCreate'、 '2012-07-26T15:24:58.523'、0x1F8B080、 '4.3。 1 ')

-ステートメント#13コミットトランザクション

-ステートメント#14 SELECT TOP(1)[c]。[Name] AS [Name] FROM [Dummies] AS [c]

ご覧のとおり、実際にデータベースを作成する前に、データベースにアクセスしようとしています4回。これは正しくないようです。既存のデータベースでアプリケーションを起動すると、実際のクエリが実行される前に、データベースに7回クエリが実行されます。これは、context.Database.Create()ではなく.Exists()で発生することに注意してください。

また、私の構成のシードメソッドは呼び出されませんが、コンストラクターは呼び出されます。

これはすべて非常に間違っていて混乱しているようです。なぜ最初にエラーが頻繁に発生するのか、そして私のシードメソッドがまったく呼び出されないのかを誰かが教えてくれることを願っています。

SqlServerCompactとEntityFrameworkの最新の安定したバージョンを使用しています。

package id = "EntityFramework" version = "4.3.1" targetFramework = "net40"

パッケージid = "Microsoft.SqlServer.Compact" version = "4.0.8854.2" targetFramework = "net40"

25
Martin1921

Entityframeworkを構成する方法はたくさんあるようで、誰もが最善のものについて独自の見解を持っています。私が提供できるのは、仕事で標準化したものに基づいた私の見解だけです。これの多くは開発者の好みです。私の好みはたまたま可能な限り制御することなので、何がいつ起こっているのかを常に正確に理解しています。

自動移行

まず、自動移行は便利かもしれませんが、特にプロジェクトが成長したり、データがより複雑になったりすると、多くの問題が発生します。私の意見では、どの商用/本番システムもこれよりも制御できるはずです。 AutomaticMigrationsEnabled = false;を設定することにより、すべての主要プロジェクトの自動移行を常にオフにします。必要なときに明示的に移行を実行します(開発では、これはVisualStudioのパッケージマネージャーコンソールでUpdate-Databaseと入力します。本番環境では、最新のコードへの移行を明示的に呼び出す独自の小さな移行ユーティリティを作成しました。 -ただし、自動化されたものはありません)。

@Terricの答えは、自動移行とデータ損失の両方が許可されていることで私を怖がらせます!列の変更が適切に実行されなかったためにデータが失われたため、ソリューションを展開して重要なデータを消去する人にはなりたくありません。補足として、devで明示的に移行を実行するとき、冗長な出力(Update-Database -v)に-vスイッチを使用することがよくあります。これにより、実行中のSQLと、必要に応じて障害/警告を確認できます。

また、開発に何度か移行した後でこれらの設定を変更してもうまくいかないことも私たちの経験です。これがどこで追跡されているかはわかりませんが、自動移行を無効にしてプロジェクトを新たに開始すると、予期しない事態が発生することはありません。

個人的には、MigrateDatabaseToLatestVersionのイニシャライザーを削除し、必要なときに(パッケージマネージャーコンソールまたは独自の明示的なコードを使用して)自分で移行を実行します。

データベースが存在しない場合は作成する

この動作は、DatabaseInitializerによって提供されます(実際にはEntityFramework自体ではありません)。 CreateDatabaseIfNotExists初期化子はEntityFrameworkに組み込まれており、一部のバージョンではデフォルトです。ただし、ここでも、推測されるアプリの動作に関するすべてのことを行っているわけではありません。私の意見では、もう少し制御したいと思います。

この男は、組み込みのCreateDatabaseIfNotExistsから継承するカスタムデータベース初期化子の例を持っています 。ただし、いつでも独自のロジックを作成して、表示したい正確なロジック(データベースの作成を含む)を実装することができます。繰り返しますが、これは予期しない動作を回避するだけです。開発者としての私の個人的な好みは、モックアップやテストプロジェクトをいじくり回しているだけでない限り、これらを厳密に制御することです。

予期しない動作のない超シンプルなカスタムDatabaseInitializer:

namespace MyProject.Data.DatabaseInitializers
{
    public class MyCustomDbInit<TContext> : IDatabaseInitializer<TContext>
        where TContext : DbContext
    {
        public void InitializeDatabase(TContext context)
        {
            // Create our database if it doesn't already exist.
            context.Database.CreateIfNotExists()

            // Do you want to migrate to latest in your initializer? Add code here!

            // Do you want to seed data in your initializer? Add code here!
        }
    }
}

結果

コードファーストアプローチを使用し、自動移行を無効にして、上記のようなカスタムDatabaseInitializerを使用すると、何がいつ発生するかを非常に適切に制御できます。

私たちはこれらの戦略を仕事で使用しており、問題はありません(ただし、これらの戦略を決定するのに多少の手間がかかりました)。うまくいけば、あなたは同様の成功を見つけるでしょう!

12
BenSwayne

SQL CEを使用して問題を再現し、最初に上記のコードを使用してEFコードを使用することができました。

奇妙なことに、私があなたのコードをそのまま使用したとき、初めて、それは完全に機能しました。問題が発生するためには、.sdfファイルの_MigrationHistoryテーブルを実際に削除する必要がありました。

.sdfファイルを削除することによって(これはあなたの場合のオプションではないかもしれませんが、それについてはさらに詳しく説明します)次に実行したときに、移行テーブルが作成されましたが、それでも正しく機能しませんでした。お気づきのように、ステップ12で最終的にテーブルが作成されます。

-ステートメント#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

-ステートメント#2 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#3警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory]

-ステートメント#4 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#5警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory]

-ステートメント#6 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#7警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory]

-ステートメント#8 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#9警告:System.Data.SqlServerCe.SqlCeException(0x80004005):指定されたテーブルは存在しません。 [__MigrationHistory]

-ステートメント#10は分離レベルでトランザクションを開始します:Serializable

-ステートメント#11 CREATE TABLE [Dummies]([DummyId] [int] NOT NULL IDENTITY、[test] nvarchar、[addThis] nvarchar、CONSTRAINT [PK_Dummies] PRIMARY KEY([DummyId]))

-ステートメント#12 CREATE TABLE [__MigrationHistory]([MigrationId] nvarchar NOT NULL、[CreatedOn] [datetime] NOT NULL、[Model] [image] NOT NULL、[ProductVersion] nvarchar NOT NULL、CONSTRAINT [PK ___ MigrationHistory] ​​PRIMARY KEY( [MigrationId])))

- ステートメント#13 INSERT INTO [__MigrationHistory]([MigrationId]、[CreatedOn]、[モデル]、[PRODUCTVERSION])VALUES( '201208101940587_InitialCreate'、 '2012-08-10T19:40:59.055'、0x1F8B0800000000000400CD57DD8ED33C10BDFF24DE21F23D75591082550A5ADA5D5441771159B89FC6D3D6C27642ECACDA67FB2E78245E8171FE9A26FDD95D40E2AE998CCF1C1F9FF1A43FFFFF11BE5D6B15DC6166656246ECD960C802347122A4598E58EE164F5FB1B76F9EFC175E0ABD0EBED679673E8F561A3B622BE7D273CE6DBC420D76A0659C253659B8419C680E22E167C3E12BFE6CC8912018610541F839374E6A2C1EE8719C98185397839A250295ADE2F4262A50836BD068538871C4DE8195F198D2AE64661D0B2E94046211A15A3C90D2F0B5A7C49A6254EE9268B9CDED26C5A2E4884D72AD37ED144AFA809B9D00853E65498A99DB7CC6457BE154B080EF2EE6DDD5CDDAEE42CF62C4A6C63D3F63C175AE14CC150516A02CB2207D791EB924C3F768300387E2133887199DCD5460B18B4A8DF3F4E5FD0479CD87675E100EC6240E1C1D748F7D87AB437F0225D1C865E419165CC9358A8F68966ED5909DC1BA8EBC180EC93A5F8C248F79842CC7F6EECAE7E3554188DB95B47FBB70C8B76EE87B842CEB4092F615A95911C1B5EB98A5CC8ED0B58E5722 91DF8297E61D9456DB4BA129B66D0C5E7646DD41FC400B8533485392A7D552552488CA7E1A3F8D1EDE37BAC4E0B1DDD33E0DDBA612191596D8794BA5EB2E9E808339F8031B0BDD4B3B296D5D6757E16E8F6E05AFF3FDEF72CDEEA532D8D3F56DE9AE68379A9AACD8183634F6DE16D5CA280605D9A1561F272AD7E6E8C5710CA76CC3364819B93F42D3526D9026D8C70979478AAED8BCA776E71AEC1EDE31D777539AEA8DFB3B2E0F2BC79D9E263D0B96292C2081EEA4F0F68B36D6A11EF88441F45D4598D1B81CE3E0859F8175DA0C8C5C90E8B7C937A43BB878F7D8C9D45CC4D60AF58F8F2769C8672707516F7ADD7FAC983BC8E215647BEEF2DF9A1A8FC47DD850E85F70A787C3E6F06828BD3962629ED0364A8ACD3C79E4DCE8774AC8DB5F67E104AD5C6E21FCB79AC1D87F1D6C41EB9CA95924B5ECB4AB36A33AA5732A337420489C8BCCC905C48E5EC7686D31D0BF82CA29E552CF514CCD4DEED2DC5D588B7AAE76E664C88FD72F86E32EE7F026F54FF64F6C81684ADA02DE9877B954A2E17DD5B7FA2108EF93AA7F88157DD010DC72D3205D27E69E40957C134CD1F8EEBB459D2A02B33726823B3CCCEDB486BB8A851309CB0C745BC132523189802AB74A5081F68A6D3DFFE782FB7F176F7E018E4F04B08F0C0000、 '4.3.1')

-ステートメント#14コミットトランザクション

-ステートメント#15 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

適切な場所にテーブルを作成することで問題を解決します。

enter image description here

これを実行すると、次にコードが実行されたときに、すべてが再び完全に実行されました。

-ステートメント#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#2 SELECT TOP(1)[c]。[ProductVersion] AS [ProductVersion] FROM [__MigrationHistory] ​​AS [c]

-ステートメント#3 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#4 SELECT [Extent1]。[MigrationId] AS [MigrationId] FROM [__MigrationHistory] ​​AS [Extent1]

-ステートメント#5 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#6 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#7 SELECT TOP(1)[Project1]。[C1] AS [C1]、[Project1]。[MigrationId] AS [MigrationId]、[Project1]。[Model] AS [Model] FROM(SELECT [Extent1 ]。[MigrationId] AS [MigrationId]、[Extent1]。[CreatedOn] AS [CreatedOn]、[Extent1]。[Model] AS [Model]、1 AS [C1] FROM [__MigrationHistory] ​​AS [Extent1])AS [ Project1] ORDER BY [Project1]。[CreatedOn] DESC

-ステートメント#8 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

-ステートメント#9 SELECT TOP(1)[Project1]。[C1] AS [C1]、[Project1]。[MigrationId] AS [MigrationId]、[Project1]。[Model] AS [Model] FROM(SELECT [Extent1 ]。[MigrationId] AS [MigrationId]、[Extent1]。[CreatedOn] AS [CreatedOn]、[Extent1]。[Model] AS [Model]、1 AS [C1] FROM [__MigrationHistory] ​​AS [Extent1])AS [ Project1] ORDER BY [Project1]。[CreatedOn] DESC

-ステートメント#10は、分離レベルでトランザクションを開始します:指定なし

-ステートメント#11は[ダミー]([テスト]、[addThis])値(null、null)を挿入します。

[ダミー]から[ダミーID]を選択します。ここで[ダミーID] = @@ IDENTITY

-ステートメント#12は[ダミー]([テスト]、[addThis])の値(null、null)を挿入します。

[ダミー]から[ダミーID]を選択します。ここで[ダミーID] = @@ IDENTITY

-ステートメント#13は[ダミー]([テスト]、[addThis])の値(null、null)を挿入します。

[ダミー]から[ダミーID]を選択します。ここで[ダミーID] = @@ IDENTITY

-ステートメント#14は[ダミー]([テスト]、[addThis])の値(null、null)を挿入します。

[ダミー]から[ダミーID]を選択します。ここで[ダミーID] = @@ IDENTITY

-ステートメント#15コミットトランザクション

-ステートメント#16 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

SDF全体を削除して再作成することができない場合は、これを元に戻すために行った方法を次に示します。

Appconfigで接続文字列を作成します(おそらく動的接続文字列が必要なため、コードに含まれていますが、これは1回限りのことです )。私の接続文字列は次のようになりました:

   <connectionStrings>
        <add connectionString="Data Source=MyContext.sdf;Persist Security Info=False;" name="MyContext" providerName="System.Data.SqlServerCe.4.0"/>
    </connectionStrings>

接続文字列を使用するように、コンテキストのコンストラクターを変更します。

public MyContext()
    //: base(string.Format(@"DataSource=""{0}""", "MyContext.sdf"))
    : base("MyContext")

これらすべてが必要なのは、パッケージマネージャーコンソールでいくつかのコマンドを実行して、テーブルを再作成できるようにするためです。パッケージマネージャーコンソールを開き、次のコマンドを実行します。

add-migration initial -ignorechanges

次に、プログラムを実行します。いくつかの警告がスローされますが、テーブルが作成され、データが入力されます。その後、コンストラクターを元に戻すことができ、問題は発生しませんでした。

注:動作を開始するとSeed関数も動作を開始しました

5
Mark Oreta

私はあなたが提供したコードをいじってみましたが、この場合は(CEの代わりにSQL Serverを使用して)次のようになりました。 Database.Createコードを削除し、EFの自動移行でそれを実行できるようにしました。これが実行され、Seedメソッドが正しく呼び出されます。

internal class Program
{
    private static void Main()
    {
        EntityFrameworkProfiler.Initialize();

        Database.DefaultConnectionFactory = new SqlConnectionFactory("System.Data.SqlServer");
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyContextConfiguration>());

        using (var context = new MyContext())
        {
            var element = context.Dummies.FirstOrDefault();
        }
    }
}

internal class Dummy
{
    public String Id { get; set; }
}

internal sealed class MyContext : DbContext
{
    public MyContext() : base(@"Data Source=localhost;Initial Catalog=Dummies;User Id=<USER_ID>;Password=<PASSWORD>;MultipleActiveResultSets=False;")
    {
    }

    public DbSet<Dummy> Dummies { get; set; }
}

internal sealed class MyContextConfiguration : DbMigrationsConfiguration<MyContext>
{
    public MyContextConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }

    protected override void Seed(MyContext context)
    {
        context.Dummies.AddOrUpdate(new Dummy() { Id = "First" });
    }
}

EFプロファイラーを見ると、DBに対して実行されるクエリが増えていることがわかります(古いEdmMetaDataテーブルのチェックもあります...これは非常に奇妙なことです。テーブルが検出された場合は削除する必要があるためです。 __MigrationHistoryテーブルの)。なぜこれが起こっているのかわかりません。これは私たちの側の構成の問題(修正方法はまだわかりません)か、移行コードのバグのどちらかだと思います。

したがって、EF移行では、コードベースの移行( ここにある私のブログ投稿を参照 )または自動移行(このコードスニペットが示すように)のいずれかに任されていると思います。時間が経つにつれて、EF(または移行方法)がこの奇妙な動作をする理由、またはEF自体が進化するにつれて良くなる理由をよりよく理解できるようになると思います。

1