FreeSqlナビゲーション属性のカスケード保存機能

12705 ワード

前に書く


FreeSql一款Netプラットフォームの下でサポートする.net framework 4.5+、.Netcore 2.1+のオープンソースORM.ユニットテストは3100+を超え、新しい開発者を引きつけ続け、生命は絶えず開発されている.
EFCoreと同様に、ナビゲーションオブジェクトもあり、「OneToOne」(一対一)、「ManyToOne」(多対一)、「OneToMany」(一対多)、「ParentChild」(親子)、「ManyToMany」(多対多)をサポートしており、エンティティ間の関連付けの構成や手動構成を約束したり、fluent apiを使用して関連付けを設定したりすることができます.
カスケード保存機能でオブジェクトの保存が可能な場合は、その「OneToMany」、「ManyToMany」ナビゲーション属性のセットも一括保存します.

メカニズム規則


[1対多]モデルでは、保存時にエンティティの属性セットをカスケードして保存できます.セキュリティの使用を考慮して、完全な比較は行われず、エンティティ属性セットの追加または更新操作のみが実現されるため、エンティティ属性セットのデータは削除されません.
完全なコントラスト機能は危険すぎるので、次のシーンを考えてみましょう.
  • が保存されている場合、エンティティの属性セットは空ですが、どのように操作しますか?すべての削除を記録しますか?
  • 保存する場合、データベースに記録が非常に多いため、サブテーブルの一部のデータを保存したいだけか、追加するだけで、どのように操作しますか?

  • 【マルチペアマルチ】モデルでは、中間テーブルの保存は完全な比較操作であり、外部エンティティの操作は新規のみとなります(更新しないことに注意してください).
  • プロパティセットが空の場合、関連するすべてのデータ(中間テーブル)
  • を削除します.
  • 属性セットが空でない場合、データベースに存在する関連データ(中間テーブル)と完全に対比して、削除および追加すべきレコード
  • を算出する.

    機能オン/オフ

    IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    
        .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=|DataDirectory|/document22.db;Pooling=true;Max Pool Size=10")
    
        .UseAutoSyncStructure(true) //          
        .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) //  SQL    ,    
        .Build();

    FreeSqlBuilderを使用して作成したIrreeSqlオブジェクト、カスケード保存機能は、デフォルトではオンです.
    グローバルクローズ:
    fsql.SetDbContextOptions(opt => opt.EnableAddOrUpdateNavigateList = false);

    ローカルオフ:
    var repo = fsql.GetRepository();
    repo.DbContextOptions.EnableAddOrUpdateNavigateList = false;

    1対のマルチ(OneToMany)コードテスト


    展示しやすいように、以下はParentChild関係ですが、実は彼もOneToManyで、自分が自分を指しているだけです.
    [Table(Name = "EAUNL_OTMP_CT")]
    class CagetoryParent
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    
        public Guid ParentId { get; set; }
        [Navigate("ParentId")]
        public List Childs { get; set; }
    }

    テストデータの初期化:
    var cts = new[] {
        new CagetoryParent
        {
            Name = "  1",
            Childs = new List(new[]
            {
                new CagetoryParent { Name = "  1_1" },
                new CagetoryParent { Name = "  1_2" },
                new CagetoryParent { Name = "  1_3" }
            })
        },
        new CagetoryParent
        {
            Name = "  2",
            Childs = new List(new[]
            {
                new CagetoryParent { Name = "  2_1" },
                new CagetoryParent { Name = "  2_2" }
            })
        }
    };

    1、一括挿入を実行する:
    var repo = g.sqlite.GetRepository();
    repo.Insert(cts);

    このメソッドを最初に実行すると、データベース・テーブルの自動作成操作が実行されます.テーブルが既に存在する場合は、比較を実行し、変更がなければ操作を実行しません.
    ブレークポイントデバッグにより、出力SQLの内容がコンソールに表示されます.
    INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f', '  1', '00000000-0000-0000-0000-000000000000'), ('5d90afcb-ed57-f6f4-0082-cb6c5b531b3e', '  2', '00000000-0000-0000-0000-000000000000')
    
    INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afcb-ed57-f6f4-0082-cb6d0c1c5f1a', '  1_1', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb6e74bd8eef', '  1_2', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb6f6267cc5f', '  1_3', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb7057c41d46', '  2_1', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'), ('5d90afcb-ed57-f6f4-0082-cb7156e0375e', '  2_2', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e')

    2、テストロット修正:
    cts[0].Name = "  11";
    cts[0].Childs.Clear();
    cts[1].Name = "  22";
    cts[1].Childs.Clear();
    repo.Update(cts);

    コンソールに出力SQLが表示されます.
    UPDATE "EAUNL_OTMP_CT" SET "Name" = CASE "Id" 
    WHEN '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f' THEN '  11' 
    WHEN '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e' THEN '  22' END 
    WHERE ("Id" IN ('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f','5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'))

    Childs.Clearは実行しましたが、コンソールはサブセット削除文を出力していません.完全な比較が行われていないことを示しています.
    3、サブセット表にすでにデータが存在し、引き続きデータを追加する
    cts[0].Name = "  111";
    cts[0].Childs.Clear();
    cts[0].Childs.Add(new CagetoryParent { Name = "  1_33" });
    cts[1].Name = "  222";
    cts[1].Childs.Clear();
    cts[1].Childs.Add(new CagetoryParent { Name = "  2_22" });
    repo.Update(cts);

    コンソールに出力SQLが表示されます.
    UPDATE "EAUNL_OTMP_CT" SET "Name" = CASE "Id" 
    WHEN '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f' THEN '  111' 
    WHEN '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e' THEN '  222' END 
    WHERE ("Id" IN ('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f','5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'))
    
    INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afe8-ed57-f6f4-0082-cb725df546ea', '  1_33', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afe8-ed57-f6f4-0082-cb7338a6214c', '  2_22', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e')

    もう一度「一対多」(OneToMany)が完全な対比を行わず、追加または更新するだけで、テストデータを追加するときに多くのコードを簡略化できることを検証しました.

    マルチペアマルチ(ManyToMany)コードテスト


    以下では3つのクラスを作成し,Songはボリュームクラス,Tagは外部クラス,SongTagは中間関連データクラスとし,命名規則を用いてナビゲーション関係設定を行った.
    [Table(Name = "EAUNL_MTM_SONG")]
    class Song
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List Tags { get; set; }
    }
    [Table(Name = "EAUNL_MTM_TAG")]
    class Tag
    {
        public Guid Id { get; set; }
        public string TagName { get; set; }
        public List Songs { get; set; }
    }
    [Table(Name = "EAUNL_MTM_SONGTAG")]
    class SongTag
    {
        public Guid SongId { get; set; }
        public Song Song { get; set; }
        public Guid TagId { get; set; }
        public Tag Tag { get; set; }
    }

    テストデータの初期化:
    var tags = new[] {
        new Tag { TagName = "  " },
        new Tag { TagName = "80 " },
        new Tag { TagName = "00 " },
        new Tag { TagName = "  " }
    };
    var ss = new[]
    {
        new Song
        {
            Name = "     .mp3",
            Tags = new List(new[]
            {
                tags[0], tags[1]
            })
        },
        new Song
        {
            Name = "  .mp3",
            Tags = new List(new[]
            {
                tags[0], tags[2]
            })
        }
    };

    1、一括挿入を実行する:
    var repo = g.sqlite.GetRepository();
    repo.Insert(ss);

    このメソッドを最初に実行すると、データベース・テーブルの自動作成操作が実行されます.テーブルが既に存在する場合は、比較を実行し、変更がなければ操作を実行しません.
    ブレークポイントデバッグにより、出力SQLの内容がコンソールに表示されます.
    INSERT INTO "EAUNL_MTM_SONG"("Id", "Name") VALUES('5d90fdb3-6a6b-2c58-00c8-37974177440d', '     .mp3'), ('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '  .mp3')
    
    INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90fdb7-6a6b-2c58-00c8-37991ead4f05', '  '), ('5d90fdbd-6a6b-2c58-00c8-379a0432a09c', '80 ')
    
    INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37974177440d', '5d90fdb7-6a6b-2c58-00c8-37991ead4f05'), ('5d90fdb3-6a6b-2c58-00c8-37974177440d', '5d90fdbd-6a6b-2c58-00c8-379a0432a09c')
    
    INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90fdcc-6a6b-2c58-00c8-379b5af59d25', '00 ')
    
    INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90fdb7-6a6b-2c58-00c8-37991ead4f05'), ('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90fdcc-6a6b-2c58-00c8-379b5af59d25')

    2、ロット更新をテストし、中間表データが変化した
    ss[0].Name = "     .mp5";
    ss[0].Tags.Clear();
    ss[0].Tags.Add(tags[0]);
    ss[1].Name = "  .mp5";
    ss[1].Tags.Clear();
    ss[1].Tags.Add(tags[3]);
    repo.Update(ss);

    コンソールに出力SQLが表示されます.
    UPDATE "EAUNL_MTM_SONG" SET "Name" = CASE "Id" 
    WHEN '5d90fdb3-6a6b-2c58-00c8-37974177440d' THEN '     .mp5' 
    WHEN '5d90fdb3-6a6b-2c58-00c8-37987f29b197' THEN '  .mp5' END 
    WHERE ("Id" IN ('5d90fdb3-6a6b-2c58-00c8-37974177440d','5d90fdb3-6a6b-2c58-00c8-37987f29b197'))
    
    SELECT a."SongId", a."TagId" 
    FROM "EAUNL_MTM_SONGTAG" a 
    WHERE (a."SongId" = '5d90fdb3-6a6b-2c58-00c8-37974177440d')
    
    DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId" = '5d90fdb3-6a6b-2c58-00c8-37974177440d' AND "TagId" = '5d90fdbd-6a6b-2c58-00c8-379a0432a09c')
    
    INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90febd-6a6b-2c58-00c8-379c21acfc72', '  ')
    
    SELECT a."SongId", a."TagId" 
    FROM "EAUNL_MTM_SONGTAG" a 
    WHERE (a."SongId" = '5d90fdb3-6a6b-2c58-00c8-37987f29b197')
    
    DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId" = '5d90fdb3-6a6b-2c58-00c8-37987f29b197' AND "TagId" = '5d90fdb7-6a6b-2c58-00c8-37991ead4f05' OR "SongId" = '5d90fdb3-6a6b-2c58-00c8-37987f29b197' AND "TagId" = '5d90fdcc-6a6b-2c58-00c8-379b5af59d25')
    
    INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90febd-6a6b-2c58-00c8-379c21acfc72')

    次のように処理されます.
  • 第1ステップ、songデータ
  • を一括更新
  • 第2ステップは、songが更新操作であるため、songの関連データ
  • を先に検出する必要がある.
  • 第3ステップでは、songの関連データ(tags[0]を除く)を削除する.tags[0]は今回保存するデータであるため、率直に言えば、今回保存しないすべての関連データ
  • を削除する.
  • 第4ステップでは、tags[3]ロック外部データを追加する.外部テーブル
  • はまだ存在しないためである.
  • 第5歩、第2歩と同じ
  • 第6歩、第3歩と同じ
  • 第7歩、中間表データを挿入し、李白.mp 5とロック関連
  • どうしてこんなにたくさんのステップがあるの?なぜならsongテストデータは2つあり、doubleであり、1つのレコードが4~5本程度であれば、追加する関連データがあるかどうかによって異なります.
    3、関連データのクリアテスト
    ss[0].Name = "     .mp4";
    ss[0].Tags.Clear();
    ss[1].Name = "  .mp4";
    ss[1].Tags.Clear();
    repo.Update(ss);

    コンソールに出力SQLが表示されます.
    DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId" = '5d90fdb3-6a6b-2c58-00c8-37974177440d')
    
    DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId" = '5d90fdb3-6a6b-2c58-00c8-37987f29b197')
    
    UPDATE "EAUNL_MTM_SONG" SET "Name" = CASE "Id" 
    WHEN '5d90fdb3-6a6b-2c58-00c8-37974177440d' THEN '     .mp4' 
    WHEN '5d90fdb3-6a6b-2c58-00c8-37987f29b197' THEN '  .mp4' END 
    WHERE ("Id" IN ('5d90fdb3-6a6b-2c58-00c8-37974177440d','5d90fdb3-6a6b-2c58-00c8-37987f29b197'))

    「ManyToMany」(マルチペアマルチ)モデルの下で、中間テーブルは完全な対比操作であり、外部テーブルは挿入され、更新されないことをもう一度証明します.

    オブジェクトのナビゲーション


    カスケード保存機能に加えて、ナビゲーションオブジェクトの主な設計目的は、lambda式のクエリー操作を実行するために、エンティティ間のポイントをすばやく挿入することです.
    ナビゲーション関係をカスタマイズする方法
    //    ,OneToMany
    [Navigate("song_id")]
    public virtual List Obj_song_tag { get; set; }
    
    //    ,ManyToOne/OneToOne
    [Navigate("song_id")]
    public virtual Song Obj_song { get; set; }
    
    //    ,ManyToMany
    [Navigate(ManyToMany = typeof(tag_song))]
    public virtual List tags { get; set; }
  • は約束することができて、約束しないことができます;
  • が約束しない場合は、Navigate特性関連を指定する必要があります.
  • 関連がない場合、クエリ時にOn条件を示すことができ、LeftJoin(a=>a.Parent.Id==a.ParentId);
  • が関連付けられている場合は、ナビゲーションオブジェクトを直接使用すればいいので、On条件は自動的に添付されます.
  • *


  • FluentApiを使用して、外部でナビゲーション関係を設定することもできます.
    fsql.CodeFirst.ConfigEntity(a => a
        .Navigate(b => b.roles, null, typeof(        ))
        .Navigate(b => b.users, "uid")
    );

    優先度、プロパティ>FluentApi

    最後に書く


    FreeSqlがリリースされてから10ヶ月が経ち、元旦には1.0の正式版がリリースされ、将来的には.Netコミュニティの下で力を与える車輪も、私が十数年も無駄にしないのではないでしょうか.Netは捨てない少しの貢献でしょう.
    FreeSqlがますます良くなることを望んでいます.
    原Netcoreはますます良くなりました!(3.0のアップグレードで多くの人が車をひっくり返したが、心の中にはその気持ちがあり、車をひっくり返したのはせいぜい悪口を言っただけで、悪口を言ったら続けなければならない)