LINQ Query with switch/case/ifelse clauses

T

TechnoDex

I'm trying to process through hierarchy of objects which use interfaces
in order to extract out some data but having difficulty with some of the
logic using Linq to Objects. So far the only solution I have seen is
using a ternary operator which is nasty and convoluted in any language.
My sample below is nasty as well (my appologies in advance). Basically
because I have either inherited objects, interfaces or collections of
things of type object, I need to cast data while processing through the
hierarchy and use the appropriate properties along the way (which may
not be the same at each level). I was hoping to avoid writing Methods
for each given type to process as well, but rather push the limits of
Linq to see what it can do. If you look below, after the two "let"
assignments is where I would like to use a switch/case/ifelse logic to
process the hierarchies in their different ways.... Any ideas???

from RefridgeItem in Refridge.ColdFood
let fFruit = RefridgeItem.Item as Fruit
let vVeggy = RefridgItem.Item as Veggy
where (fFruit != null)
from Apple aApple in fFruit.Items.OfType<Apple>()
where aApple.Color == Color.Red
select RefridgeItem
where (vVeggy != null)
from (Pepper pPepper in vVeggy.Items.OfType<Pepper>()
where pPepper.Color == Color.Red
select RefridgeItem


public IFood
{
Color FoodColor { get; set:}
decimal Weight { get; set; }
}

public Food : IFood
{
Color FoodColor { get; set:}
decimal Weight { get; set; }
}

public Veggy
{
IEnum<object> Items { get; set; }
}

public Pepper : Fruit
{
}

public Tomato : Fruit
{
}


public Fruit
{
IEnum<object> Items { get; set; }
}

public Orange : Food
{

}

public Apple : Food
{

}

public RefrigeItem
{

object Item
}

public Refridge
{
//A collection of RefridgeItem objects
RefridgeItems ColdFood { get; set; }
}
 
P

Peter Duniho

[...] Basically
because I have either inherited objects, interfaces or collections of
things of type object, I need to cast data while processing through the
hierarchy and use the appropriate properties along the way (which may
not be the same at each level). I was hoping to avoid writing Methods
for each given type to process as well, but rather push the limits of
Linq to see what it can do. If you look below, after the two "let"
assignments is where I would like to use a switch/case/ifelse logic to
process the hierarchies in their different ways.... Any ideas???

Perhaps you could rephrase the question. It's hard for me to understand
exactly the point of the operation.

Your code starts out enumerating items from "Refridge.ColdFood", but then
once it finds (for example) a "Fruit", it goes looking for a _specific
item_ of "Fruit", filtering on the "Red" ones. It does the same thing for
"Veggy". In both cases, you apparently restrict the search to specific
items: Apple for Fruit and Pepper for Veggy. But there's no apparent
rhyme or reason in your example for why you have code that only wants the
Red Apples and Red Peppers but not, for example, the Red Tomatoes or Red
Radishes.

If you _do_ have a specific need to only take the Apples and Peppers from
the group of all Red items, it seems to me that it would be better to
select all the Red items first, and then filter those according to the
type. You can use Enumerable.SelectMany() to aggregate all the Items
collections from your ColdFood members, and the do a simpler LINQ query on
that result. For example:

Type[] types = { typeof(Apple), typeof(Pepper) };

var result = from FoodItem in
Refridge.ColdFood.SelectMany(RefridgeItem => RefridgeItem.Items)
where FoodItem.Color == Color.Red &&
types.Contains(FoodItem.GetType());

Your description says you are selectively choosing properties unique to
the objects, but your code example suggests exactly the opposite: that
ultimately, the filtering is done on properties common to the objects. So
I'd think the above should work (or some variant thereof).

If you really are filtering on different properties depending on the type
of the object, then it would be helpful if you could elaborate on that.
Not only in what way you're filtering, as well as providing an example of
that sort of filtering, but also why you feel justified in creating a new
enumeration of objects that don't actually have anything in common with
respect to each other.

Thanks,
Pete
 
T

TechnoDex

Pete

From your explainations, I think you have a handle on what I'm trying to
achieve, but I will try to rephrase the question. Is there a way to use
switch, case, or if/else logic inside of LINQ to process a query of a
headerogenious hierarchy object filtering data at various differently
levels along the way??

A closer type Hierarchy would be more like this:

public Veggy
{
IVeggiesPreference VeggiesILike { get; set; }
IVeggiesPreference VeggiesIDontLike { get; set; }
}
public IVeggiesPreference
{
IEnum<object> Items {get; set;}
}
public GoodVeggies : IVeggiesPreference
{
IEnum<object> Items {get; set;}
}
public GoodVeggies : IVeggiesPreference
{
IEnum<object> Items {get; set;}
}
public VeggyFood
{
Color VeggyColor {get; set;}
}
public Pepper : VeggyFood
{
}
public Radishes: VeggyFood
{
}


public Fruit
{
IEnum<object> Items { get; set; }
}
public FruitFood
{
Color FruitColor {get; set;}
}
public Orange : FruitFood
{
}
public Apple : FruitFood
{
}


from RefridgeItem in Refridge.ColdFood
let fFruit = RefridgeItem.Item as Fruit
let vVeggy = RefridgItem.Item as Veggy
where (fFruit != null)
from Apple aApple in fFruit.Items.OfType<Apple>()
where aApple.FruitColor == Color.Red
select RefridgeItem
where (vVeggy != null)
from (Pepper pPepper in vVeggy.VeggiesILike.Items.OfType<Pepper>()
where pPepper.VeggyColor == Color.Red
select RefridgeItem


[...] Basically
because I have either inherited objects, interfaces or collections of
things of type object, I need to cast data while processing through
the hierarchy and use the appropriate properties along the way (which
may not be the same at each level). I was hoping to avoid writing
Methods for each given type to process as well, but rather push the
limits of Linq to see what it can do. If you look below, after the
two "let" assignments is where I would like to use a
switch/case/ifelse logic to process the hierarchies in their
different ways.... Any ideas???

Perhaps you could rephrase the question. It's hard for me to
understand exactly the point of the operation.

Your code starts out enumerating items from "Refridge.ColdFood", but
then once it finds (for example) a "Fruit", it goes looking for a
_specific item_ of "Fruit", filtering on the "Red" ones. It does the
same thing for "Veggy". In both cases, you apparently restrict the
search to specific items: Apple for Fruit and Pepper for Veggy. But
there's no apparent rhyme or reason in your example for why you have
code that only wants the Red Apples and Red Peppers but not, for
example, the Red Tomatoes or Red Radishes.

Due to the Business needs, I am only interested in a particular subset of
the hierarchy of objects (mainly the Apples and the Peppers, but not the
Radishes, etc) for processing. You are correct about my sample classes in
my example as they are lacking some of the differences in level hierarchy
from the other object types for simplicity sake (I have tried to enhance
the example above with more details).
If you _do_ have a specific need to only take the Apples and Peppers
from the group of all Red items, it seems to me that it would be
better to select all the Red items first, and then filter those
according to the type. You can use Enumerable.SelectMany() to
aggregate all the Items collections from your ColdFood members, and
the do a simpler LINQ query on that result. For example:

Type[] types = { typeof(Apple), typeof(Pepper) };

var result = from FoodItem in
Refridge.ColdFood.SelectMany(RefridgeItem => RefridgeItem.Items)
where FoodItem.Color == Color.Red &&
types.Contains(FoodItem.GetType());

Basically I'm trying to use Linq without the extras against an object model
to retrieve data. In the example you've provided, you are using Lamda
expressions which is effectively an anonymous method which is evaluated at
the last possible moment. The SelectMany sounds like it might have some
promise though, as I'm interested in the same type of "Parent" object in
the hierarch tree, but only if particular details in the children are
present.
Your description says you are selectively choosing properties unique
to the objects, but your code example suggests exactly the opposite:
that ultimately, the filtering is done on properties common to the
objects. So I'd think the above should work (or some variant
thereof).

This is due to my bad example, hopefully the new one provides better
details and differences.
If you really are filtering on different properties depending on the
type of the object, then it would be helpful if you could elaborate
on that. Not only in what way you're filtering, as well as providing
an example of that sort of filtering, but also why you feel justified
in creating a new enumeration of objects that don't actually have
anything in common with respect to each other.

I'm interested in the "common parent object" types for items which contain
child hierarchy data matching particular business requirements.
 
P

Peter Duniho

Pete

From your explainations, I think you have a handle on what I'm trying to
achieve, but I will try to rephrase the question. Is there a way to use
switch, case, or if/else logic inside of LINQ to process a query of a
headerogenious hierarchy object filtering data at various differently
levels along the way??

I'll try to answer the basic question. Later, when I have more time, I'll
look at your elaboration and see if I have anything else to offer.

With respect to "is there a way to use switch, case, or if/else logic
inside of LINQ", the answer is "yes, sort of".

Everything in the query itself has to resolve to an expression. You can't
write flow-control statements as part of the LINQ itself. But, if you can
put the switch/case or if/else inside a lambda expression's statement body
and cause the result of that switch/case or if/else to flow out of the
anonymous method represented by the lambda as its return value and thus
affect the outcome of the LINQ expression in the way you want, then you
can use them that way.

Hope that helps.

Pete
 
H

Harlan Messinger

I'm trying to process through hierarchy of objects which use interfaces
in order to extract out some data but having difficulty with some of the
logic using Linq to Objects. So far the only solution I have seen is
using a ternary operator which is nasty and convoluted in any language.
My sample below is nasty as well (my appologies in advance). Basically
because I have either inherited objects, interfaces or collections of
things of type object, I need to cast data while processing through the
hierarchy and use the appropriate properties along the way (which may
not be the same at each level). I was hoping to avoid writing Methods
for each given type to process as well, but rather push the limits of
Linq to see what it can do. If you look below, after the two "let"
assignments is where I would like to use a switch/case/ifelse logic to
process the hierarchies in their different ways.... Any ideas???

from RefridgeItem in Refridge.ColdFood
let fFruit = RefridgeItem.Item as Fruit
let vVeggy = RefridgItem.Item as Veggy
where (fFruit != null)
from Apple aApple in fFruit.Items.OfType<Apple>()
where aApple.Color == Color.Red
select RefridgeItem
where (vVeggy != null)
from (Pepper pPepper in vVeggy.Items.OfType<Pepper>()
where pPepper.Color == Color.Red
select RefridgeItem

Are you looking for Union? Suppose we create base class Country with a
Name field, and for no good reason derive EuropeanCountries which add a
Currency and AsianCountries which add a Capital:

public abstract class Country
{
public Country(string name) { Name = name; }
public string Name { get; set; }
}

public class EuropeanCountry : Country
{
public EuropeanCountry(string name, string currency) : base(name)
{
Currency = currency;
}
public string Currency { get; set; }
}

public class AsianCountry : Country
{
public AsianCountry(string name, string capital) : base(name)
{
Capital = capital;
}
public string Capital { get; set; }
}

public static void DoCountries()
{
Country[] countries = new Country[] {
new EuropeanCountry("France", "euro"),
new EuropeanCountry("Denmark", "krone"),
new EuropeanCountry("Albania", "lek"),
new EuropeanCountry("Italy", "euro"),
new EuropeanCountry("Spain", "euro"),
new AsianCountry("Japan", "Tokyo"),
new AsianCountry("China", "Peking"),
new AsianCountry("Iran", "Tehran"),
new AsianCountry("Cambodia", "Phnom Penh"),
new AsianCountry("India", "New Delhi"),
new AsianCountry("Uzbekistan", "Tashkent")
};

// Get the European countries with euros and the
// Asian countries whose capitals start with "T".

var query = (from country in countries
where country is EuropeanCountry
let country2 = (EuropeanCountry)country
where country2.Currency.Equals("euro")
select new { Name = country2.Name, Quality =
country2.Currency })
.Union(from country in countries
where country is AsianCountry
let country2 = (AsianCountry)country
where country2.Capital.StartsWith("T")
select new { Name = country2.Name, Quality =
country2.Capital });
foreach (var country in query)
Console.WriteLine("{0}, {1}", country.Name, country.Quality);

}
 
P

Peter Duniho

[...]
If you _do_ have a specific need to only take the Apples and Peppers
from the group of all Red items, it seems to me that it would be
better to select all the Red items first, and then filter those
according to the type. You can use Enumerable.SelectMany() to
aggregate all the Items collections from your ColdFood members, and
the do a simpler LINQ query on that result. For example:

Type[] types = { typeof(Apple), typeof(Pepper) };

var result = from FoodItem in
Refridge.ColdFood.SelectMany(RefridgeItem => RefridgeItem.Items)
where FoodItem.Color == Color.Red &&
types.Contains(FoodItem.GetType());

Basically I'm trying to use Linq without the extras against an object
model
to retrieve data.

What does "without the extras" mean? By "object model", does that mean
you are using in-memory objects, as opposed to LINQ to SQL?
In the example you've provided, you are using Lamda
expressions which is effectively an anonymous method which is evaluated
at
the last possible moment.

Any expression you write in a LINQ expression, when used with in-memory
objects (i.e. "LINQ to objects"), will be translated to an anonymous
method. The example I posted has two different anonymous methods: one
explicitly created by the lambda expression, in the call to SelectMany(),
and one implicitly created by the boolean expression "FoodItem.Color ==
Color.Red && types.Contains(FoodItem.GetType())".
The SelectMany sounds like it might have some
promise though, as I'm interested in the same type of "Parent" object in
the hierarch tree, but only if particular details in the children are
present.

As I mentioned before, you can put switch/case or if/else statements into
a lambda expression's method body, but you can't use them directly in a
LINQ expression. If you want to do that, then my previous example changes
the second, implicit, anonymous method into an explicit one so that you
can provide a full block statement for the lambda:

var result = Refridge.ColdFood
.SelectMany(RefridgeItem => RefridgeItem.Items)
.Where(FoodItem =>
{
if (FoodItem is Apple)
{
return ((Apple)FoodItem).FruitColor ==
Color.Red;
}
else if (FoodItem is Pepper)
{
return ((Pepper)FoodItem).VeggyColor ==
Color.Red;
}

return false;
});

Note, of course, that as the way of expressing your criteria becomes more
imperative and less functional, you retain less and less of the elegance
of LINQ. IMHO, as this happens, that tends to suggest it would be better
to express the solution differently.

For example, you could create a Dictionary<Type, Func<Object, Color>> that
you can then refer to within a LINQ expression. It requires some up-front
setup, but overall one might consider it more maintainable. Something
like this:

KeyValuePair<Type, Func<Object, Color>>[] rgkvp = new
{
new KeyValuePair<Type, Func<Object, Color>>(typeof(Apple),
apple => ((Apple)apple).FruitColor),
new KeyValuePair<Type, Func<Object, Color>>(typeof(Pepper),
pepper => ((Pepper)pepper).VeggyColor),
};

Dictionary<Type, Func<Object, Color>> dictObjToColor = new
Dictionary<Type, Func<Object, Color>>(rgkvp.Length);

foreach (KeyValuePair<Type, Func<Object, Color>> kvp in rgkvp)
{
dictObjToColor.Add(kvp.Key, kvp.Value);
}

var result = from FoodItem in Refridge.ColdFood(RefridgeItem =>
RefridgeItem.Items)
where dictObjToColor.Contains(typeof(FoodItem)) &&
dictObjToColor[typeof(FoodItem)] == Color.Red

There are more lines of code up-front (barely), but as you can see, each
new criteria you want to add can be done in just a single line of code,
rather than a whole new if/else clause, and the LINQ query itself is still
simple.

If the criteria among the different objects isn't always even looking at
the same type value (e.g. you want to find all Red FruitFoods, but you
want to find all Organic VeggyFoods), then you can change the dictionary
to use a Func<Object, bool> instead of Func<Object, Color>, and put the
comparison logic inside the lambda expression used to initialize the
dictionary, instead of in the LINQ query itself. Doing that gives you
more flexibility with respect to what you can express, at the cost of
specializing the dictionary in a way that could prevent you from being
able to use it in a generalized way (e.g. you can't set up a single
dictionary and then use it to look first for all Red FoodItems, and then
later for all Green FoodItems, as you could with the example I show above).

You could even simplify the KeyValuePair[] initialization by writing a
simple generic method to reduce typing:

static KeyValuePair<TKey, Func<Object, TResult>> CreateKVP<TKey,
TResult>(TKey key, Func<Object, TResult> value)
{
return new KeyValuePair<TKey, Func<Object, TResult>>(key, value);
}

Then, let type inference fill in the details during the initialization:

KeyValuePair<Type, Func<Object, Color>>[] rgkvp = new
{
CreateKVP(typeof(Apple), apple => ((Apple)apple).FruitColor),
CreateKVP(typeof(Pepper), pepper =>
((Pepper)pepper).VeggyColor),
};

(At least, I think that should work...it's from memory, without having a
compiler handy at the moment to test it. My apologies if I've overlooked
some limitation of type interfence that prevents it from working).

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top