Search code examples
c#linqexpression-treeslinqkit

Linq Expression for matching a complex many-to-many relationship


Let's say I have two entities:

public class Animal {
  public int ID { get; set; }
  public string Name { get; set; }
  public bool EatsVegetables { get; set; }
  public bool EatsMeat { get; get; }
  public bool EatsFruits { get; set; }
}

public bool Food {
  public int ID { get; set; }
  public string Name { get; set; }
  public bool ContainsVegetables { get; set; }
  public bool ContainsMeat { get; get; }
  public bool ContainsFruits { get; set; }
}

(We'll consider the animals as being able to eat anything that doesn't contain anything they're not able to eat.) Now given a particular Food, I'd like to find out which animals I can feed it to, and given a particular animal, I'd like to find out what I can feed it.

public static Expression<Func<Animal, IEnumerable<Food>>> GetAllowedFoods(MyDataContext db) {
  return a => db.Foods.Where(f => 
    (a.EatsVegetables || !f.ContainsVegetables)
    && (a.EatsMeat || !f.ContainsMeat)
    && (a.EatsFruits || !f.ContainsFruits));
}

The converse expression looks strikingly similar:

public static Expression<Func<Food, IEnumerable<Animal>>> GetAllowedAnimals(MyDataContext db) {
  return f => db.Animals.Where(a => 
    (a.EatsVegetables || !f.ContainsVegetables)
    && (a.EatsMeat || !f.ContainsMeat)
    && (a.EatsFruits || !f.ContainsFruits));
}

So what would really make sense is a combined expression:

public static Expression<Func<Animal, Food, bool>> IsAllowedDiet() {
  return (a, f) => 
    (a.EatsVegetables || !f.ContainsVegetables)
    && (a.EatsMeat || !f.ContainsMeat)
    && (a.EatsFruits || !f.ContainsFruits));
}

And somehow the two methods above, GetAllowedFoods and GetAllowedAnimals should invoke the expression IsAllowedDiet.

I assume this is something that LinqKit should be able to do in a snap, but as a rank beginner with LinqKit, I haven't a clue what the correct syntax would be!


Solution

  • I worked it out. The method IsAllowedDiet() remains as expressed in the question. GetAllowedFoods() and GetAllowedAnimals() are expressed as follows:

    public static Expression<Func<Animal, IEnumerable<Food>>> GetAllowedFoods(MyDataContext db) {
      var isAllowed = IsAllowedDiet();
      return a => db.Foods.AsExpandable().Where(f => isAllowed.Invoke(a, f));
    }
    
    public static Expression<Func<Food, IEnumerable<Animal>>> GetAllowedAnimals(MyDataContext db) {
      var isAllowed = IsAllowedDiet();
      return f => db.Animals.AsExpandable().Where(a => isAllowed.Invoke(a, f));
    }
    

    I'm starting to enjoy LinqKit! :-)