【问题标题】:Shadow Properties to access related entity with Table-Per-Hierarchy inheritance model使用 Table-Per-Hierarchy 继承模型访问相关实体的影子属性
【发布时间】:2017-01-17 18:01:04
【问题描述】:

我正在为 ASP.Net Core 中的网站构建菜单系统。假设我有几个数据库表,一个用于Pages,一个用于Articles,尽管它们是不同的实体才真正重要。他们每个人都有一个NamePermalink 属性。

在我还想存储在数据库中的菜单中,我想引用每个实体的NamePermalink。我设计了一个简单的菜单类/模型结构如下:

抽象菜单项

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&lt;T&gt;

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 不支持延迟加载。因此,当我在存储库中获取菜单项时,我想急切地加载影子属性,特别是相关实体。

two approaches 可以访问影子属性。我更新存储库方法的第一种方法如下所示:

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&lt;TProperty&gt;([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 =&gt; Page.Name

【问题讨论】:

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


    【解决方案1】:

    不幸的是,目前没有用于预先加载派生类属性的语法(请注意,它们与影子属性不同)。这与缺少延迟加载一起使显式加载成为唯一的选择。例如,请参阅ef-core load collection property of nested tph inherited member 如何将其用于单个项目,对于项目集合,恐怕您必须将结果具体化为列表,然后使用具体类型的显式加载并依赖 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));
    

    【讨论】:

    • 好吧,我对术语的误解。这有助于我首先弄清楚出了什么问题。我会看看你的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 2012-08-10
    • 2011-02-08
    • 1970-01-01
    • 2011-06-08
    相关资源
    最近更新 更多