EF Core Query One To One Self Relation

35 views Asked by At

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))));

0

There are 0 answers