【发布时间】:2017-01-17 18:01:04
【问题描述】:
我正在为 ASP.Net Core 中的网站构建菜单系统。假设我有几个数据库表,一个用于Pages,一个用于Articles,尽管它们是不同的实体才真正重要。他们每个人都有一个Name 和Permalink 属性。
在我还想存储在数据库中的菜单中,我想引用每个实体的Name 和Permalink。我设计了一个简单的菜单类/模型结构如下:
抽象菜单项
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;
有了这一切,我希望我可以为每个项目获得Name 和Permalink,如下所示(例如,在视图中):
@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<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。
【问题讨论】:
标签: c# linq asp.net-core-mvc entity-framework-core navigation-properties