1. ホーム
  2. entity-framework

[解決済み] 1000個のEntity Frameworkオブジェクトを作成するとき、いつSaveChanges()を呼び出すべきですか?(インポート中のように)

2023-05-15 19:07:13

質問

各実行で 1000 件のレコードを持つインポートを実行しています。 私の仮定についていくつか確認したいことがあります。

これらのうちどれが最も理にかなっていますか。

  1. 実行する SaveChanges() それぞれ AddToClassName() を呼び出します。
  2. 実行 SaveChanges() それぞれ n AddToClassName() の呼び出しがあります。
  3. 実行 SaveChanges() その後 全て AddToClassName() を呼び出します。

最初のオプションは、おそらく遅いですよね? メモリ内の EF オブジェクトを分析し、SQL を生成するなどの作業が必要になるためです。

2番目のオプションは、トライキャッチの周りにラップすることができるので、両方の世界のベストであると仮定します。 SaveChanges() を呼び出すことができ、失うのは n の数のレコードを失うだけです。 各バッチをList<>に格納するとよいでしょう。 もし SaveChanges() の呼び出しが成功したら、リストを取り除く。 失敗したら、項目を記録する。

最後のオプションは、おそらく非常に遅くなるでしょう。なぜなら、すべてのEFオブジェクトが SaveChanges() が呼び出されるまで、すべてのEFオブジェクトがメモリ上になければならないからです。 そして、保存が失敗した場合、何もコミットされませんよね?

どうすれば解決するのでしょうか?

念のため、まずテストしてみます。パフォーマンスはそれほど悪くはないはずです。

1つのトランザクションですべての行を入力する必要がある場合、AddToClassNameクラスのすべての後にそれを呼び出します。行を独立して入力できる場合は、すべての行の後で変更を保存します。データベースの整合性は重要です。

2番目のオプションは好きではありません。システムにインポートして、1000 行のうち 10 行が拒否され、1 行が悪いという理由だけで拒否されると、(最終ユーザーの観点から)私は混乱するでしょう。10 件のインポートを試してみて、失敗したら、1 件ずつ試して、ログに記録することができます。

時間がかかるかどうかテストしてください。propably」と書かないでください。まだ分かっていないのだから。実際に問題になったときだけ、他の解決策を考える(marc_s)。

EDIT

いくつかのテストをしてみました(時間はミリ秒)。

10000行です。

1行後のSaveChanges():18510,534

100行後のSaveChanges():4350,3075

10000行後のSaveChanges():5233,0635

50000行目

1行後のSaveChanges():78496,929

500行後のSaveChanges():22302,2835

50000行後のSaveChanges():24022,8765

つまり、n行後にコミットする方が、すべて後にコミットするよりも実際には速いのです。

私のお勧めは

  • n行後にSaveChanges()を実行します。
  • 1つのコミットが失敗したら、それを1つずつ試して、欠陥のある行を見つけます。

クラスをテストします。

TABLEです。

CREATE TABLE [dbo].[TestTable](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [SomeInt] [int] NOT NULL,
    [SomeVarchar] [varchar](100) NOT NULL,
    [SomeOtherVarchar] [varchar](50) NOT NULL,
    [SomeOtherInt] [int] NULL,
 CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

クラスです。

public class TestController : Controller
{
    //
    // GET: /Test/
    private readonly Random _rng = new Random();
    private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private string RandomString(int size)
    {
        var randomSize = _rng.Next(size);

        char[] buffer = new char[randomSize];

        for (int i = 0; i < randomSize; i++)
        {
            buffer[i] = _chars[_rng.Next(_chars.Length)];
        }
        return new string(buffer);
    }


    public ActionResult EFPerformance()
    {
        string result = "";

        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>";
        TruncateTable();

        return Content(result);
    }

    private void TruncateTable()
    {
        using (var context = new CamelTrapEntities())
        {
            var connection = ((EntityConnection)context.Connection).StoreConnection;
            connection.Open();
            var command = connection.CreateCommand();
            command.CommandText = @"TRUNCATE TABLE TestTable";
            command.ExecuteNonQuery();
        }
    }

    private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows)
    {
        var startDate = DateTime.Now;

        using (var context = new CamelTrapEntities())
        {
            for (int i = 1; i <= noOfRows; ++i)
            {
                var testItem = new TestTable();
                testItem.SomeVarchar = RandomString(100);
                testItem.SomeOtherVarchar = RandomString(50);
                testItem.SomeInt = _rng.Next(10000);
                testItem.SomeOtherInt = _rng.Next(200000);
                context.AddToTestTable(testItem);

                if (i % commitAfterRows == 0) context.SaveChanges();
            }
        }

        var endDate = DateTime.Now;

        return endDate.Subtract(startDate);
    }
}