使用Table-Per-Hierarchy繼承模型訪問相關實體的Shadow Properties

asp.net-core-mvc c# entity-framework-core linq navigation-properties

我正在為ASP.Net Core中的網站構建一個菜單系統。讓我們假設我有幾個數據庫表,一個用於Pages ,一個用於Articles ,儘管它們只是它們是不同的實體才真正重要。它們中的每一個都具有NamePermalink屬性。

在我想要存儲在數據庫中的菜單中,我想引用每個實體的NamePermalink 。我設計了一個簡單的菜單類/模型結構如下:

抽象MenuItem

public abstract class MenuItem
{
    [Key]
    public int MenuItemId { get; set; }
    public int MenuPosition { get; set; }
    public abstract string Name { get; }
    public abstract string Permalink { get; }
}

具體ArticleMenuItem

public class ArticleMenuItem : MenuItem
{
    public ArticleMenuItem() {}
    public ArticleMenuItem(Article article)
    {
        Article = article;
    }

    public string Name => Article.Name;
    public string Permalink => Article.Permalink;

    [Required]
    public int ArticleId { get; set; }
    [ForeignKey("ArticleId")]
    public Article Article { get; set; }
}

具體的PageMenuItem

public class PageMenuItem : MenuItem
{
    public PageMenuItem() {}
    public PageMenuItem(Page page)
    {
        Page = page;
    }

    public string Name => Page.Name;
    public string Permalink => Page.Permalink;

    [Required]
    public int PageId { get; set; }
    [ForeignKey("PageId")]
    public Page Page{ get; set; }
}

然後我為相關的DbContext重寫onModelCreating(ModelBuilder modelBuilder) ,因為我不想使單獨的DbSet<T>'s可用:

modelBuilder.Entity<PageMenuItem>();
modelBuilder.Entity<ArticleMenuItem>();

除了為菜單添加相關的DbSet<T>

public virtual DbSet<MenuItem> MenuItems { get; set; }

在加載應用程序時,將幾個示例記錄添加到數據庫中(假設我已經初始化了一些文章和頁面):

List<MenuItem> items = new List<MenuItem>()
{
    new PageMenuItem(pages[0]) { MenuPosition = 1 },
    new ArticleMenuItem(articles[0]) { MenuPosition = 2 }
};
items.ForEach(item => context.MenuItems.Add(item));

從數據庫中獲取菜單項的簡單存儲庫方法:

public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems;

有了這一切,我希望我可以獲得每個項目的NamePermalink ,如下所示(例如,在視圖中):

@foreach (MenuItem item in Model)
{
    <a href="@item.Permalink">@item.Name</a>
}

遺憾的是,這會導致null object exception ,然後我記得EF Core不支持延遲加載。因此,當我獲取存儲庫中的菜單項時,我想急切地加載陰影屬性,特別是相關實體。

訪問陰影屬性有兩種方法 。我更新我的存儲庫方法的第一種方法如下所示:

public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
    .Include(item => context.Entry(item).Property("Page").CurrentValue)
    .Include(item => context.Entry(item).Property("Article").CurrentValue)

這導致:

InvalidCastException:無法將類型為“System.Linq.Expressions.InstanceMethodCallExpression1”的對象強制轉換為“System.Linq.Expressions.MemberExpression”。

分別轉換為(Page)(Aticle)導致:

InvalidOperationException:屬性表達式'item => Convert(value(InfoSecipediaWeb.Infrastructure.EntityFramework.ApplicationDbContext).Entry(item).Property(“Page”)。CurrentValue)'無效。表達式應表示屬性訪問:'t => t.MyProperty'。

訪問陰影屬性的第二種方法似乎只能訪問單個屬性值:

public static TProperty Property<TProperty>([NotNullAttribute] object entity, [NotNullAttribute][NotParameterized] string propertyName);

但是,嘗試一下:

public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
    .Include(item => EF.Property<Page>(item, "Page"))
    .Include(item => EF.Property<Article>(item, "Article"));

結果是:

InvalidOperationException:屬性表達式'item => Property(item,“Page”)'無效。表達式應表示屬性訪問:'t => t.MyProperty'。

我想知道是否可以使用陰影屬性進行導航模型的導航?如果是這樣,我如何包含相關實體,以便在我的具體MenuItem類中可以訪問它?例如,對於public string Name => Page.Name

一般承認的答案

不幸的是,目前沒有預先加載派生類屬性的語法(請注意它們與陰影屬性不同)。這與缺乏延遲加載一起使得顯式加載成為唯一的選擇。例如,參見嵌套tph繼承成員的ef-core load collection屬性如何將它用於單個項目,對於項目集合我恐怕必須將結果具體化為列表,然後使用顯式加載具體類型並依靠EF導航屬性修復。

對於你的例子,它可能是這樣的:

public IEnumerable<MenuItem> GetAllMenuItems()
{
    var menuItems = _context.MenuItems.ToList();
    _context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article).Load();
    _context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page).Load();
    return menuItems;
}

另一個解決方法(我只說一個)是使用手動聯合查詢,它基本上殺死了TPH的想法:

public IEnumerable<MenuItem> GetAllMenuItems() =>
    _context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article)
    .AsEnumerable() // to avoid runtime exception (EF Core bug)
    .Concat<MenuItem>(
    _context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page));


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow