获取所有相关配方,无需位置过滤



这是我的类:

public class Recipe
{
/// <summary>
/// PK
/// </summary> 
public Guid Id { get; set; }
[Required]
// here should be the Id, for the initial (, in its related) recipes
public Guid BaseId { get; set; }
/// <summary>
/// All related recipes
/// </summary>
public virtual ICollection<Recipe> RelatedRecipes { get; set; }
//...
}

实际上,我想通过一个语句获取所有RelatedRecipes,而无需额外的Where(x=>...过滤。

这是我的上下文:

return await context.Recipes.Include(b => b.RelatedRecipes)...

我该怎么办?

如果 BaseId 是让配方指向它的父配方,那么它必须是可为空的,因为至少需要一个没有父配方的顶级配方。

从那里您可以配置配方之间的关系:

public class Recipe
{
public Guid Id { get; set; }
public virtual Recipe BaseRecipe { get; set; }
public virtual ICollection<Recipe> RelatedRecipes { get; set; } = new List<Recipe>();
}

对于 EF6

public class RecipeConfiguration : EntityTypeConfiguration<Recipe>
{
public RecipeConfiguration()
{
ToTable("Recipes");
HasKey(x => x.Id)
.Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasOptional(x => x.BaseRecipe)
.WithMany(x => x.RelatedRecipes)
.Map(x => x.MapKey("BaseId"));
}
}

此示例不包括实体中定义的 BaseId。您可以在实体中映射 FK,但这会给您留下 2 个事实来源。(基本 ID 与 BaseRecipe.Id(导航属性 (BaseRecipe( 更灵活,使用导航属性时,可以通过使用 Map 解析没有属性的 FK 来避免错误。(或 EF Core 中的阴影属性(

要获取食谱及其相关食谱:

var recipe = context.Recipes.Include(x => x.BaseRecipe).Include(x => x.RelatedRecipes)
.Single(x => x.Id == recipeId);

这里需要注意的是,您永远不应该尝试序列化这样的实体,例如将其从控制器返回到视图。默认情况下,序列化程序将触及实体上的每个属性,这将导致针对基本配方和相关配方以及每个基本配方和相关配方启动延迟加载查询。序列化程序在遇到自引用实体时将放弃对象图,这意味着异常或 #null 引用。

实体结构还应始终反映应用程序背后的真实数据状态。加载相关实体时,您无法"过滤"它们,例如尝试以下内容:

var recipe = context.Recipes.Include(x => x.BaseRecipe).Include(x => x.RelatedRecipes.Where(r => r.IsActive))
.Single(x => x.Id == recipeId);

例如,仅加载与"活动"相关的配方等。以上情况是不允许的。相关配方将始终反映与检索到的数据相关的完整配方集。

将此类结构传递给视图或筛选数据时,请使用Select为视图所需的数据填充简单的视图模型结构。这样,您可以将数据筛选为视图所需的内容,并在以后需要加载完整的实体状态以进行更改时将相关 ID 传回。

例如,要获取配方,并且要传递给视图的活动相关配方:

var recipe = context.Recipes
.Select(x => new RecipeViewModel
{
Id = x.Id,
Name = x.Name,
RelatedRecipes = x.RelatedRecipes
.Where(r => r.IsActive)
.Select(r => new RecipeViewModel
{
Id = r.Id,
Name = r.Name
}).ToList()
}).Single(x => x.Id == recipeId);

这将构建一个查询,以仅检索视图需要显示的数据,精确到活动相关配方的一个级别。序列化程序可以将其转换为 JSON 等,而不必担心在循环实体引用上跳闸或为延迟加载触发额外的查询。请注意,使用Select时,您无需对任何相关详细信息使用Include。EF 将根据查询解决这些问题。 仅在加载完整实体时需要Include,例如稍后要加载实体以应用更新时。

编辑:BaseRecipe 参考不是必需的,但可用于确定配方是否具有基础以及它适用于哪个。如果没有 BaseRecipe 引用,则可能需要基本 ID 来区分配方是否具有基础:

public class Recipe
{
public Guid Id { get; set; }
public Guid? BaseId { get; set; }
public virtual ICollection<Recipe> RelatedRecipes { get; set; } = new List<Recipe>();
}
public class RecipeConfiguration : EntityTypeConfiguration<Recipe>
{
public RecipeConfiguration()
{
ToTable("Recipes");
HasKey(x => x.Id)
.Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasMany(x => x.RelatedRecipes)
.WithOptional()
.HasForeignKey(x => x.BaseId);
}
}

在这种情况下,所选配方是否有基础将基于x.BaseId.HasValue

最新更新