In EF Core I want to manage a collection of Parameters (Entity Parameter). A Parameter is basically a named Value and can belong to a Recipe or can be global (no Recipe). Therefore the Reference from Parameter To Recipe is nullable. A Parameter is identified by its Name. You can say a Recipe Parameter shadows the global parameter of the same Name. Additionall all Recipe Parameter holds a Reference to the according global Parameter with the same Name. The Constraint is set To Cascade, so if a global Parameter is deleted also all Recipe Parameters will be deleted too.
#nullable enable
public class Recipe
{
[Key]
public int Id { get; private set; }
public string Name { get; set; }
public List<Parameter> Parameters { get; set; }
}
public class Parameter
{
[Key]
public int Id { get; private set; }
public Parameter? Parent { get; set; }
public Recipe? Recipe { get; set; }
public string Name { get; set; }
public double Value { get; set; }
}
#nullable restore
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parameter>()
.HasOne(e => e.Parent)
.WithMany()
.IsRequired(false)
.OnDelete(DeleteBehavior.ClientCascade);
}
The following Tables shows an Example:
| Id | Name |
|---|---|
| 1 | Recipe1 |
| 2 | Recipe2 |
| Id | ParentId | RecipeId | Name | Value |
|---|---|---|---|---|
| 1 | null | null | aaa | 1.0 |
| 2 | null | null | bbb | 2.0 |
| 3 | null | null | ccc | 3.0 |
| 4 | 2 | 1 | bbb | 22.0 |
| 5 | 2 | 2 | bbb | 222.0 |
| 6 | 3 | 2 | ccc | 333.0 |
using var db = new ParameterContext();
var recipe1 = new Recipe() { Name = "Recipe1" };
await db.Recipes.AddAsync(recipe1);
var recipe2 = new Recipe() { Name = "Recipe2" };
await db.Recipes.AddAsync(recipe2);
var aaaGlobal = new Parameter() { Name = "aaa", Value = 1 };
await db.Parameters.AddAsync(aaaGlobal);
var bbbGlobal = new Parameter() { Name = "bbb", Value = 2 };
await db.Parameters.AddAsync(bbbGlobal);
var cccGlobal = new Parameter() { Name = "ccc", Value = 3 };
await db.Parameters.AddAsync(cccGlobal);
var bbbRecipe1 = new Parameter() { Name = "bbb", Value = 22, Recipe = recipe1, Parent = bbbGlobal };
await db.Parameters.AddAsync(bbbRecipe1);
var bbbRecipe2 = new Parameter() { Name = "bbb", Value = 222, Recipe = recipe2, Parent = bbbGlobal };
await db.Parameters.AddAsync(bbbRecipe2);
var cccRecipe2 = new Parameter() { Name = "ccc", Value = 333, Recipe = recipe2, Parent = cccGlobal };
await db.Parameters.AddAsync(cccRecipe2);
await db.SaveChangesAsync();
Is it possible to select with one EF Query (SQL on Server) a set of all Parameters for a Recipe + the global Parameters but only in one instance, i.e. each Name appears only once? In Example Parameters "bbb" and "ccc" are shadowed by Recipe2 (Id 5 and Id 6).
The Result shall look like:
| Id | Name | Value |
|---|---|---|
| 1 | aaa | 1.0 |
| 5 | bbb | 222.0 |
| 6 | ccc | 333.0 |
I can use the following Code but than the removing of shadowed global Parameters is done by LINQ at Client side instead SQL on Server side.
var query = db.Parameters.Include(p => p.Recipe).Include(p => p.Parent).Where(p => p.Recipe == recipe2 || p.Recipe == null);
var parametersForRecipe2AndAllGlobal = await query.ToArrayAsync();
var idsOfShadowedGlobalParameters = parametersForRecipe2AndAllGlobal.Select(p => p.Parent?.Id).OfType<int>().ToArray();
var parametersForRecipe2 = parametersForRecipe2AndAllGlobal.Where(p => !idsOfShadowedGlobalParameters.Contains(p.Id)).ToArray();
Console.WriteLine(String.Join(Environment.NewLine, parametersForRecipe2.Select(p => (p.Id, p.Name, p.Value))));