使用 Table-Per-Hierarchy 继承模型访问相关实体的影子属性
Shadow Properties to access related entity with Table-Per-Hierarchy inheritance model
我正在 ASP.Net Core 中为网站构建菜单系统。假设我有几个数据库表,一个用于 Pages
,一个用于 Articles
,尽管真正重要的是它们是不同的实体。他们每个人都有一个 Name
和 Permalink
属性.
在我也想存储在数据库中的菜单中,我想引用每个实体的 Name
和 Permalink
。我设计了一个简单的菜单 class/model 结构如下:
抽象菜单项
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: Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.MemberExpression'.
转换为 (Page)
和 (Aticle)
分别导致:
InvalidOperationException: The property expression 'item => Convert(value(InfoSecipediaWeb.Infrastructure.EntityFramework.ApplicationDbContext).Entry(item).Property("Page").CurrentValue)' is not valid. The expression should represent a property access: '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: The property expression 'item => Property(item, "Page")' is not valid. The expression should represent a property access: 't => t.MyProperty'.
我想知道是否可以通过继承模型使用阴影属性进行导航?如果是这样,我如何包含相关实体以便在我的具体 MenuItem
类 中可以访问它?例如public string Name => Page.Name
.
不幸的是,目前没有预先加载派生的 class 属性的语法(请注意它们与影子属性不同)。这与延迟加载的缺乏一起使得显式加载成为唯一的选择。例如,请参阅 如何将它用于单个项目,对于项目集合恐怕你必须将结果具体化到列表中,然后使用具体类型的显式加载并依赖 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));
我正在 ASP.Net Core 中为网站构建菜单系统。假设我有几个数据库表,一个用于 Pages
,一个用于 Articles
,尽管真正重要的是它们是不同的实体。他们每个人都有一个 Name
和 Permalink
属性.
在我也想存储在数据库中的菜单中,我想引用每个实体的 Name
和 Permalink
。我设计了一个简单的菜单 class/model 结构如下:
抽象菜单项
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: Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.MemberExpression'.
转换为 (Page)
和 (Aticle)
分别导致:
InvalidOperationException: The property expression 'item => Convert(value(InfoSecipediaWeb.Infrastructure.EntityFramework.ApplicationDbContext).Entry(item).Property("Page").CurrentValue)' is not valid. The expression should represent a property access: '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: The property expression 'item => Property(item, "Page")' is not valid. The expression should represent a property access: 't => t.MyProperty'.
我想知道是否可以通过继承模型使用阴影属性进行导航?如果是这样,我如何包含相关实体以便在我的具体 MenuItem
类 中可以访问它?例如public string Name => Page.Name
.
不幸的是,目前没有预先加载派生的 class 属性的语法(请注意它们与影子属性不同)。这与延迟加载的缺乏一起使得显式加载成为唯一的选择。例如,请参阅
对于您的示例,它可能是这样的:
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));