EF Core 中處理 1對1 關系


最近在開發記錄感想功能的時候用到了1對1的數據關系,具體情況是這樣的,有這樣兩個1對1的類型

public class Item
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Note Note { get; set; }
}

public class Note
{
    public int Id { get; set; }
    public string Content { get; set; }
    public int ItemId { get; set; }
    public Item Item { get; set; }
    public bool Deleted { get; set; }
}

它們的1對1關系配置如下:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Note>(e =>
    {
        e.HasOne(x => x.Item).WithOne(x => x.Note).HasForeignKey<Note>(x => x.ItemId);
        e.HasQueryFilter(x => !x.Deleted);
    });
}

Note是軟刪除的,這里配置了一個QueryFilter

然后我們用dotnet-ef命令構建數據庫,生成的腳本如下:

IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL
BEGIN
    CREATE TABLE [__EFMigrationsHistory] (
        [MigrationId] nvarchar(150) NOT NULL,
        [ProductVersion] nvarchar(32) NOT NULL,
        CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
    );
END;

GO

CREATE TABLE [Items] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    CONSTRAINT [PK_Items] PRIMARY KEY ([Id])
);

GO

CREATE TABLE [Notes] (
    [Id] int NOT NULL IDENTITY,
    [Content] nvarchar(max) NULL,
    [ItemId] int NOT NULL,
    [Deleted] bit NOT NULL,
    CONSTRAINT [PK_Notes] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Notes_Items_ItemId] FOREIGN KEY ([ItemId]) REFERENCES [Items] ([Id]) ON DELETE CASCADE
);

GO

CREATE UNIQUE INDEX [IX_Notes_ItemId] ON [Notes] ([ItemId]);

GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20190813141425_InitEntities', N'2.2.6-servicing-10079');

GO

再造一條數據,方便測試

USE [demo]
GO

INSERT INTO [dbo].[Items]
           ([Title])
     VALUES
           ('a')
GO

不出意外的話,這個ItemId會是1

業務代碼如下:

[ApiController]
[Route("[controller]")]
public class NoteController : ControllerBase
{
    private readonly DemoContext _db;
    public NoteController(DemoContext db)
    {
        _db = db;
    }

    [HttpGet]
    public IEnumerable<Note> Get()
    {
        return _db.Notes.ToList();
    }

    [HttpPost]
    public void Post()
    {
        var item = _db.Items.Include(x => x.Note).FirstOrDefault(x => x.Id == 1);
        if (item != null)
        {
            item.AddNote(DateTime.Now.ToString("F"));
            _db.SaveChanges();
        }
    }

    [HttpDelete]
    public void Delete()
    {
        var item = _db.Items.Include(x => x.Note).FirstOrDefault(x => x.Id == 1);
        if (item != null)
        {
            item.DeleteNote();
            _db.SaveChanges();
        }
    }
}

就是對Id==1Item新增/修改/刪除Note

有這樣一個很簡單的場景,用戶先新增了Note,然后刪除Note,再想新增Note,這時候你就會發現數據庫報錯了:Note違反了唯一性約束。

由於Note是軟刪除的,所有當再次新增Note的時候就會出現重復的ItemId

解決這個問題的思路也很簡單,只需要把這個外鍵的唯一性約束更改為過濾掉Deleted的數據進行約束。

更改關系配置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Note>(e =>
    {
        e.HasOne(x => x.Item).WithOne(x => x.Note).HasForeignKey<Note>(x =x.ItemId);
        e.HasQueryFilter(x => !x.Deleted);
        e.HasIndex(x => x.ItemId).IsUnique().HasFilter($"[{nameof(Note.Deleted)}]=0");
    });
}

給這個ItemId的唯一性約束加一個條件e.HasIndex(x => x.ItemId).IsUnique().HasFilter($"[{nameof(Note.Deleted)}]=0");

再用dotnet-ef命令生成的數據庫更新腳本,如下:

DROP INDEX [IX_Notes_ItemId] ON [Notes];

GO

CREATE UNIQUE INDEX [IX_Notes_ItemId] ON [Notes] ([ItemId]) WHERE [Deleted]=0;

GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20190813144240_FilterIndex', N'2.2.6-servicing-10079');

GO

用有條件的INDEX替換了原先的INDEX

現在再次執行先前的業務,新增,刪除,再次新增就正常了。

完整代碼github


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM