テーブル単位の継承モデルを使用して関連するエンティティにアクセスするためのシャドウプロパティ

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

質問

ASP.Net CoreのWebサイトのメニューシステムを構築しています。私はいくつかのデータベーステーブルを持っていると仮定しましょう。一つはPages 、もう一つはArticles用ですが、実際には異なるエンティティであることが重要です。それぞれにNamePermalinkプロパティがあります。

データベースにも保存したい私のメニューでは、各エンティティのNamePermalinkを参照したいと思います。私は簡単なメニュークラス/モデル構造を次のように考案しました:

Abstract 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; }
}

個々のDbSet<T>'s使用可能にしたくないので、関連するDbContext onModelCreating(ModelBuilder modelBuilder)をオーバーライドします。

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となりnull object exception 。そして、私はEF Coreが遅延ロードをサポートしていないことを思い出しました。ですから、リポジトリ内のメニューアイテムを取得するときに、シャドウプロパティ、特に関連するエンティティを熱心に読み込みたいと思います。

シャドウプロパティにアクセスするには2つの方法があります。私のリポジトリメソッドを更新した最初のアプローチは次のようになりました:

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'。

シャドウプロパティにアクセスするための2番目の方法は、単一のプロパティ値にアクセスできるようにしているようです。

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ロード・コレクション・プロパティーを参照してください。アイテムを1つのアイテムに使用する方法、結果をリストに具体化してから具体的なタイプを明示的にロードする必要がある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;
}

もう1つの回避策(私は1つしか言っていませんでした)は、基本的に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
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ