Archives for : LINQ

As I’m sure you can tell by the title of this post that it is about deferred execution in LINQ. When a LINQ query (I know, it’s like saying, “NIC card” or “ATM machine”) is created the values are not immediatly determined. Only when the list is utilized are the results determined.

This bit me at first so hopefully with this it won’t bite you.

If you have:

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = from product in products
                  where product.Quantity == 0
                  select product;

foreach (var stock in zeroInStock)
{
    products.Remove(stock);
}

you will get:

InvalidOperationException was unhandled. Collection was modified; enumeration operation may not execute.

This is due to the deferred execution of LINQ. The results of zeroInStock are not calculated until you need them. The problem is that if you attempt to remove an item from the list in which the result originated from the original list is now not valid for the LINQ query. So how do you combat this? With ToList(), ToArray() and ToDictionary().

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                  where product.Quantity == 0
                  select product).ToList();

foreach (var stock in zeroInStock)
{
    products.Remove(stock);
}

Now this code works. In this example ToList() and ToArray() could have been used interchangbly since we’re utilizing var but you may need one over the other. By calling ToList() or ToArray() it forces execution of the query. This means that now you can change the original list all you want without any issues.

ToDictionary() works a bit different. You have to specify a lambda expression to detemine what the key should be for each given result. Here’s the ToDictionary() example:

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                  where product.Quantity == 0
                  select product.ToDictionary(x => x.Name);

foreach (var stock in zeroInStock)
{
    products.Remove(stock.Value);
}

You can see here the lambda expression means that the generated dictionary will be made of KeyValuePair<string, Product>. Since the product is now the value of the KeyValuePair the foreach that removes products with zero in stock now uses the Value.

If you are going from dictionary to dictionary as in:

Dictionary<int, Product> products = new Dictionary<int, Product>();
products.Add(1, new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(2, new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(3, new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                   where product.Value.Quantity == 0
                   select product).ToDictionary(x => x.Key);

foreach (var stock in zeroInStock)
{
    products.Remove(stock.Key);
}

you will most likely want to just use the existing key as I have above. Now, this doesn’t mean you can’t change the key but for my purposes I just used the existing key since I can use that to just remove from the dictionary.

That’s all for now. Leave me a comment if you have any questions.

Brian

LINQ Any and All Quantifiers with group by

Now that we’ve touched on “group by” lets throw in a couple of where clauses on the group by with any and all.

If you are really interested in all the data I have the code at the end of this post showing how I add all the products. Since this is just sample data I threw it in a list but chances are you’re going to be getting your list from some sort of DAL.

Let’s start with some lead up to “Any”.

Given:

string[] words = { "cow", "believe", "bread", "river", "receipt", "field" };
var wordsList =
    from w in words
    where w.Contains("ei")
    select w;

we can see this gives a list of words that have “ei” in them. But what if we don’t care for the list? What if we just want to know if any of the words have “ei”? We can simplify life by using the Any on a list from linq and get a boolean.

bool iAfterE = words.Any(w => w.Contains("ei"));

is true.

“No kidding”

“Oh great, folks, if you don’t know already this is Ivan.”

“So next you’re going to tell me I can use the All method on lists to tell me if all of the words contain “ei”? Come on and give your audience some credit. If you’re head wasn’t stuck so far…”

“Ivan, I get the point, I’ll make this easy for all of us and give them the code.”

“And don’t you think this whole talking to yourself think is getting old? I mean come on!”

“You think you can do better?”

“Listen to yourself. You’re talking to me who just happens to me you. What does me doing any better have to do with anything? I am you. Wow, pull yourself together and get this overwith. We have a database rewrite to review today, implement tomorrow and then a week to recode the app for the new database, time to move on.”

“Fine, here” (Note: I couldn’t do it, it looked like crap when I just put up all the code. I have to break it up a little bit so I’m ignoring Ivan because it was just a bad suggestion.)

string[] words = { "cow", "believe", "bread", "river", "receipt", "field" };
var wordsList =
    from w in words
    where w.Contains("ei")
    select w;

bool iAfterE = words.All(w => w.Contains("ei"));

As I’m sure you know, iAfterE is false as not all the words have “ei” in them.

List<Product> products = GetProductList();

var productGroups =
    from p in products
    group p by p.Category into g
    where g.Any(p => p.UnitsInStock == 0)
    select new { Category = g.Key, Products = g };

Console.WriteLine("nFor Any:");
foreach (var group in productGroups)
{
    Console.WriteLine("nFor Category {0}:", group.Category);
    foreach (var p in group.Products)
    {
        Console.Write("{0} with {1} units, ", p.ProductName, p.UnitsInStock);
    }
}

In this first product group you can see that the where on the group by is only creating groups by category where any of the products in the category have 0 units in stock. This is key to the where with group by, only if the where is satisfied will the group be created. You will end up with:

For Any:
For Category Condiments:
Aniseed Syrup with 13 units, Chef Anton’s Cajun Seasoning with 53 units, Chef Anton’s Gumbo Mix with 0 units, Grandma’s Boysenberry Spread with 120 units, Northwoods Cranberry Sauce with 6 units, Genen Shouyu with 39 units,
For Category MeatPoultry:
Mishi Kobe Niku with 29 units, Alice Mutton with 0 units, Thüringer Rostbratwurst with 0 units, Perth Pasties with 0 units, Tourtière with 21 units,
For Category DairyProducts:
Queso Cabrales with 22 units, Queso Manchego La Pastora with 86 units, Gorgonzola Telino with 0 units, Mascarpone Fabioli with 9 units,

List<Product> products = GetProductList();
var productGroups =
    from p in products
    group p by p.Category into g
    where g.All(p=> p.UnitsInStock > 0)
    select new { Category = g.Key, Products = g };

Console.WriteLine("nFor All:");
foreach (var group in productGroups)
{
    Console.WriteLine("nFor Category {0}:", group.Category);
    foreach (var p in group.Products)
    {
        Console.Write("{0} with {1} units, ", p.ProductName, p.UnitsInStock);
    }
}

For the second product group it is looking to make sure that all products in the group of a UnitsInStock of greater then 0 thus giving you:

For All:
For Category Seafood:
Ikura with 31 units, Konbu with 24 units, Carnarvon Tigers with 42 units, Nord-Ost Matjeshering with 10 units, Inlagd Sill with 112 units, Gravad lax with 11 units,
For Category Produce:
Uncle Bob’s Organic Dried Pears with 15 units, Tofu with 35 units, Rössle Sauerkraut with 26 units,
For Category Beverages:
Chai with 39 units, Chang with 17 units, Guaraná Fantástica with 20 units, Sasquatch Ale with 111 units, Steeleye Stout with 20 units,

Well, that’s a quick write up on any and all with group by. Time to start reviewing ERDs.

Brian

And here is GetProductList:

private List<Product> GetProductList()
{
    List<Product> products = new List<Product>();
    products.Add(new Product { ProductId = 10, ProductName = "Ikura", Category = "Seafood", UnitPrice = 31.0000, UnitsInStock = 31 });
    products.Add(new Product { ProductId = 13, ProductName = "Konbu", Category = "Seafood", UnitPrice = 6.0000, UnitsInStock = 24 });
    products.Add(new Product { ProductId = 18, ProductName = "Carnarvon Tigers", Category = "Seafood", UnitPrice = 62.5000, UnitsInStock = 42 });
    products.Add(new Product { ProductId = 30, ProductName = "Nord-Ost Matjeshering", Category = "Seafood", UnitPrice = 25.8900, UnitsInStock = 10 });
    products.Add(new Product { ProductId = 36, ProductName = "Inlagd Sill", Category = "Seafood", UnitPrice = 19.0000, UnitsInStock = 112 });
    products.Add(new Product { ProductId = 37, ProductName = "Gravad lax", Category = "Seafood", UnitPrice = 26.0000, UnitsInStock = 11 });
    products.Add(new Product { ProductId = 7, ProductName = "Uncle Bob's Organic Dried Pears", Category = "Produce", UnitPrice = 30.0000, UnitsInStock = 15 });
    products.Add(new Product { ProductId = 14, ProductName = "Tofu", Category = "Produce", UnitPrice = 23.2500, UnitsInStock = 35 });
    products.Add(new Product { ProductId = 28, ProductName = "Rössle Sauerkraut", Category = "Produce", UnitPrice = 45.6000, UnitsInStock = 26 });
    products.Add(new Product { ProductId = 3, ProductName = "Aniseed Syrup", Category = "Condiments", UnitPrice = 10.0000, UnitsInStock = 13 });
    products.Add(new Product { ProductId = 4, ProductName = "Chef Anton's Cajun Seasoning", Category = "Condiments", UnitPrice = 22.0000, UnitsInStock = 53 });
    products.Add(new Product { ProductId = 5, ProductName = "Chef Anton's Gumbo Mix", Category = "Condiments", UnitPrice = 21.3500, UnitsInStock = 0 });
    products.Add(new Product { ProductId = 6, ProductName = "Grandma's Boysenberry Spread", Category = "Condiments", UnitPrice = 25.0000, UnitsInStock = 120 });
    products.Add(new Product { ProductId = 8, ProductName = "Northwoods Cranberry Sauce", Category = "Condiments", UnitPrice = 40.0000, UnitsInStock = 6 });
    products.Add(new Product { ProductId = 15, ProductName = "Genen Shouyu", Category = "Condiments", UnitPrice = 15.5000, UnitsInStock = 39 });
    products.Add(new Product { ProductId = 9, ProductName = "Mishi Kobe Niku", Category = "MeatPoultry", UnitPrice = 97.0000, UnitsInStock = 29 });
    products.Add(new Product { ProductId = 17, ProductName = "Alice Mutton", Category = "MeatPoultry", UnitPrice = 39.0000, UnitsInStock = 0 });
    products.Add(new Product { ProductId = 29, ProductName = "Thüringer Rostbratwurst", Category = "MeatPoultry", UnitPrice = 123.7900, UnitsInStock = 0 });
    products.Add(new Product { ProductId = 53, ProductName = "Perth Pasties", Category = "MeatPoultry", UnitPrice = 32.8000, UnitsInStock = 0 });
    products.Add(new Product { ProductId = 54, ProductName = "Tourtière", Category = "MeatPoultry", UnitPrice = 7.4500, UnitsInStock = 21 });
    products.Add(new Product { ProductId = 11, ProductName = "Queso Cabrales", Category = "DairyProducts", UnitPrice = 21.0000, UnitsInStock = 22 });
    products.Add(new Product { ProductId = 12, ProductName = "Queso Manchego La Pastora", Category = "DairyProducts", UnitPrice = 38.0000, UnitsInStock = 86 });
    products.Add(new Product { ProductId = 31, ProductName = "Gorgonzola Telino", Category = "DairyProducts", UnitPrice = 12.5000, UnitsInStock = 0 });
    products.Add(new Product { ProductId = 32, ProductName = "Mascarpone Fabioli", Category = "DairyProducts", UnitPrice = 32.0000, UnitsInStock = 9 });
    products.Add(new Product { ProductId = 1, ProductName = "Chai", Category = "Beverages", UnitPrice = 18.0000, UnitsInStock = 39 });
    products.Add(new Product { ProductId = 2, ProductName = "Chang", Category = "Beverages", UnitPrice = 19.0000, UnitsInStock = 17 });
    products.Add(new Product { ProductId = 24, ProductName = "Guaraná Fantástica", Category = "Beverages", UnitPrice = 4.5000, UnitsInStock = 20 });
    products.Add(new Product { ProductId = 34, ProductName = "Sasquatch Ale", Category = "Beverages", UnitPrice = 14.0000, UnitsInStock = 111 });
    products.Add(new Product { ProductId = 35, ProductName = "Steeleye Stout", Category = "Beverages", UnitPrice = 18.0000, UnitsInStock = 20 });
    return products;
}

LINQ group by and GroupBy

I initially starting using LINQ as it was easy to order the objects in a list without having to write a Comparer. Just write your lambda expression and BOOM!, list sorted.

I want to take this thought a step further, and as implied by the post title, do a group by.

Starting, here is an order by % 2 giving us a list of even and then odd numbers:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var orderedNumbers = from n in numbers
                     orderby n % 2 == 0 descending
                     select n;

foreach (var g in orderedNumbers)
{
    Console.Write("{0},", g);
}

This is all pretty straight forward, order by numbers that when modded by 2 are 0 and we have the numbers 4,8,6,2,0,5,1,3,9,7.

But what if I want to simply have two lists, one with evens and one with odds? That’s where group by comes in.

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var numberGroups = from n in numbers
                   group n by n % 2 into g
                   select new { Remainder = g.Key, Numbers = g };

foreach (var g in numberGroups)
{
    if(g.Remainder.Equals(0))
        Console.WriteLine("Even Numbers:", g.Remainder);
    else
        Console.WriteLine("Odd Numbers:", g.Remainder);
    foreach (var n in g.Numbers)
    {
        Console.WriteLine(n);
    }
}

with the output:

Odd Numbers:
5
1
3
9
7
Even Numbers:
4
8
6
2
0

What’s happening here is that LINQ is using anonymous types to create new dictionary (actually a System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<int, int>>).

It is important to note here that the key here that everything is keyed on is the first value after the “by”.

Taking this one simple step forward let’s group a bunch of words. The following doesn’t work quite right:

string[] words = { "blueberry", "Chimpanzee", "abacus", "Banana", "apple", "cheese" };

var wordGroups = from w in words
                 group w by w[0] into g
                 select new { FirstLetter = g.Key.ToString().ToLower(), Words = g };

foreach (var g in wordGroups)
{
    Console.WriteLine("Words that start with the letter '{0}':", g.FirstLetter);
    foreach (var w in g.Words)
    {
        Console.WriteLine(w);
    }
}

giving us the output:

Words that start with the letter 'b':
blueberry
Words that start with the letter 'c':
Chimpanzee
Words that start with the letter 'a':
abacus
apple
Words that start with the letter 'b':
Banana
Words that start with the letter 'c':
cheese

That’s because there is a bit of a red herring here. Remember that the first value after the by is what is used to group by. In our case w[0] for Chimpanzee is “C”, not c. If we change it to:

string[] words = { "blueberry", "Chimpanzee", "abacus", "Banana", "apple", "cheese" };

var wordGroups = from w in words
                 group w by w[0].ToString().ToLower() into g
                 select new { FirstLetter = g.Key.ToString().ToLower(), Words = g };

foreach (var g in wordGroups)
{
    Console.WriteLine("Words that start with the letter '{0}':", g.FirstLetter);
    foreach (var w in g.Words)
    {
        Console.WriteLine(w);
    }
}

then we get the results we expect with:

Words that start with the letter 'b':
blueberry
Banana
Words that start with the letter 'c':
Chimpanzee
cheese
Words that start with the letter 'a':
abacus
apple

Taking this even one step further we can throw an orderby above the group and order things alphabetically:

var wordGroups = from w in words
orderby w[0].ToString().ToLower()
group w by w[0].ToString().ToLower() into g
select new { FirstLetter = g.Key.ToString().ToLower(), Words = g };

So let’s now make this a bit over the top complex. Given the classes:

public class Customer
{
    public List<Order> Orders { get; set; }
}

public class Order
{
    public DateTime Date { get; set; }
    public int Total { get; set; }
}

lets group a customer list by customer, then by year, then by month:

List<Customer> customers = GetCustomerList();
 
var customerOrderGroups = from c in customers
                          select
                              new {c.CompanyName,
                                   YearGroups = from o in c.Orders
                                                group o by o.OrderDate.Year into yg
                                                select
                                                    new {Year = yg.Key,
                                                         MonthGroups = from o in yg
                                                         group o by o.OrderDate.Month into mg
                                                         select new { Month = mg.Key, Orders = mg }
                                                    }
                                  };

Whew! that took a lot to copy and paste from MSDN’s sample library! 😉
As mentioned previously the important part here is that the keys for these are the first value after the “by”. This just creates a bunch of dictionarys keyed embeded together keyed on the values after the “by”.

The GroupBy method that is a part of Linq can also take an IEqualityComparer. Given the comparer:

public class AnagramEqualityComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        return getCanonicalString(x) == getCanonicalString(y);
    }

    public int GetHashCode(string obj)
    {
        return getCanonicalString(obj).GetHashCode();
    }

    private string getCanonicalString(string word)
    {
        char[] wordChars = word.ToCharArray();
        Array.Sort<char>(wordChars);
        return new string(wordChars);
    }
}

we can find all the matching anagrams. This is possible because the IEqualityComparer compares words based on a sorted array of characters. If you take “meat” and “team” they both become “aemt” when sorted by their characters.

string[] anagrams = { "from", "salt", "earn", "last", "near", "form" };

var orderGroups = anagrams.GroupBy(
                      w => w.Trim(),
                      a => a.ToUpper(),
                      new AnagramEqualityComparer()
                  );

foreach (var group in orderGroups)
{
    Console.WriteLine("For the word "{0}" we found matches to:", group.Key);
    foreach (var word in group)
    {
        Console.WriteLine(word);
    }
}

Like the inline Linq, here the first value is the key and the second value is what to put into the list. The last value is the IEqualityComparer I mentioned earler. We don’t get double entries since “last” will match “salt” and there is no reason, therefore, to add a new key.

That’s all for now.

Brian

T1, T2, T3, T4, TResult, Hut, Hut, Hike

This post is kind of not LINQ related, kind of LINQ related.

I’ve mentioned lambdas before but wanted to move more into what is more at the core of lambdas. That is more along the lines of delegates.

We all know about delegates, don’t we?

“No Brian, we don’t.”

“Ivan is that you?” (Yes, I’ve named my inner voice Ivan, IV, inner voice, Ivan, get it?)

“Yes”

“Um, is that all?”

“What else do you want me to say? I want to know about delegates”

Ok, well, then, delegates are basically pointers to methods.

Let me give you an example:

public partial class Window1 : Window
{
    delegate int del(int i);

    public Window1()
    {
        del myDelegate = x => x * x;
        int square = myDelegate(5);
    }
}

You can see here that I have defined my type for the delegate below the class declaration

delegate int del(int i);

The delegate, myDelegate, takes an int and returns an int. Then inline with anycode I can define a method that uses these parameters.

Now I could do something like:

del myDelegate2 = x => --x;
int decrement = myDelegate2(4);

but without having to declare a delegate type let’s take a look at Func so we can create functions inline without having to declare a delegate type first. We technically are declaring a type because of generics and we are limited to the small set of parameters that Func allows us, but that’s neither here nor there since I would assume, for an inline query this should be able to handle your needs. If you can’t do it in what Func provides I suspect you will probably just do a standard method.

Func types are:

Func(TResult) Delegate
Func(T, TResult) Delegate
Func(T1, T2, TResult) Delegate
Func(T1, T2, T3, TResult) Delegate
Func(T1, T2, T3, T4, TResult) Delegate

So let’s take a look at some code:

int[] list1 = { 0, 1, 2, 3, 4, 5 };
Func<int, bool> lookForEven = x => x % 2 == 0;
var myListOfEvens = list1.Where(lookForEven);

The Where method in the int list takes Func<int, bool> predicate. (It will take Func<int, int, bool> predicate as well). So I created the method lookForEven with a lambda, x => x % 2 == 0, and then I can use it in the Where method. Now, the operations of the method don’t have to be defined inline. They can be a method that matches the types of the declaration.

private bool BadlyNamedMethod(int val)
{
    return Math.Pow(val, 2) < 20;
}

private void TestMyMethod()
{
    int[] list1 = { 0, 1, 2, 3, 4, 5 };
    var sqrsLessThenTwenty = list1.Where(BadlyNamedMethod);
}

Now let’s look at taking multiple inputs:

int[] list1 = { 0, 1, 2, 3, 4, 5 };
int[] list2 = { 0, 2, 4, 6, 8 };
Func<int, int, bool> myFunc = (x,y) => x == y;
foreach (var x in list1)
{
    foreach (var y in list2)
    {
        if (myFunc(x, y))
        {
            MessageBox.Show("Found Match");
            break;
        }
    }
}

Basically I’m creating an intersect.

“Brian, there is an intersect function already.”

“Yes Ivan, I know”

“Fine, f-off”

Yes, yes, yes, as Ivan pointed out I could just as easily done:

var myListOfEquals = list1.Intersect(list2);

but then you wouldn’t see me using the Func.

Basically, I’m creating a function that takes two parameters, x and y, and testing for equality. The system knows what I’m going to do becuase when I created “myFunc” I defined that it would take int, int and return bool.

But we’re not limited to primitives.

private void TestMyMethod()
{
    Person[] people = {new Person {FirstName = "Brian", LastName = "Mullen", Id = 1},
			   new Person {FirstName = "George", LastName = "Smith", Id = 2},
			   new Person {FirstName = "Mabel", LastName = "Jones", Id = 3}};

    Func<Person, string> compareByName = p => p.LastName;
    var orderedByLastName = people.OrderBy(compareByName);
}

Want to compare people by a list of strings?

Func<Person, string, int?> personCompare = (x, y) =>
{
    if (x.FirstName == y)
        return x.Id;
    return null;
};

Anyways, have to go. But hopefully this is a nice introductory look to delegates, Func and some lambdas.

Brian

The labmda says, “baaaaa”

 LINQ allows you do define simple functions/methods in-line with the query you are doing.  These are called lambda expressions.

They have a special format to allow for in-line expressions.  

Take a look at:

c => c + 1

Basically this is:
for each value c give me c + 1

Rather then complicating things with full-blown LINQ queries lets cheat and just use simple arrays.  For example:

int[] myInts = { 1, 9, 2, 8, 3, 7, 4, 6, 5 };
var largeInts = myInts.Where(x => x > 5);

The where clause requires a lambda expression that returns a boolean.  Here we’re saying:

for each value x give me values where x > 5

Um, Brian, I have graduated college, this is kindergarten stuff.

Fine, lets go a bit more complicated:

string[] digits = { “zero”, “one”, “two”, “three”, “four”, “five”, “six”, “seven”, “eight”, “nine” };
var shortDigits = digits.Where((digit, index) => digit.Length < index);

That’s two, yes, two variables here. If you haven’t realized by now LINQ is really syntactic sugar.  When using arrays with LINQ, since all it is really going to do is just iterate over the values for you, you have access to an index variable that represents the index of where the query is.

Here it is saying
for each digit and its corresponding index give me the values where the length of the digit string is less then the index

meaning shortDigits ends up having the values:

{ “five”, “six”, “seven”, “eight”, “nine” }

Now, before we go too far I need to take a step back and clarify something from the last post.  Remember I gave a basic LINQ statement as:

var sortedFiles = from file in files orderby file.LastWriteTime descending select file;

and then went on to say:

from file in files

basically for each file in our files list

orderby file.LastWriteTime

LastWriteTime is a property on FileInfo.  files is a list of FileInfo, therefore each file is a FileInfo object.  Intelli-sense is fully supported here so when you type in file and put the dot is shows you all the properties on file as if it were a standardly declared FileInfo.

descending

Like a standard DESC to an orderby clause.  Like sql it implicitly orders by ASC so if you want descending you have to add it.

select file

return the file that applies to the orderby clause.  This may seem a bit redundant and I would agree but it is still needed.

Okay, now that you have read this for a second time I really want to address that last section.  It is only redundant when you want the full object available in the list.  If all I wanted was a list of file names I would do:

var sortedFiles = from file in files orderby file.LastWriteTime descending select file.Name;

Yes, you can return a single property from the object.

BUT WAIT, THERE’S MORE!

Yes, Ron Popeil here with the amazing Create-The-Object-On-The-Fly ability of LINQ and the 3.5 framework.

That’s right, Set It and Forget it! and it will generate types ON THE FLY!!!

Say I not only want the file name but also want the time when the file was created (CreationTime).  Well, just add that to your LINQ and now you have an IEnumerable with two properties:

var sortedFiles = from file in files orderby file.LastWriteTime descending select new { file.Name, file.CreationTime };

Yes, here you can see the real power of the var.  sortedFiles is now a list consisting of some object (really a System.Linq.Enumerable.SelectIterator<System.IO.FileInfo,<string,System.DateTime>> object) that has the properties Name and CreationTime.  This ability is called Anonymous Types.

Taking this idea one step further, remember that a LINQ query is really just SQL you can put in your code to enumerate over lists.

That’s it Brian, your f-in with me!

Yes, yes I am.

Wow, deja vu.

As you have seen the ability to create anonymous types on the fly as well as using lambda expressions aren’t really just simply enumerating over a list.  But back to the sql idea.  Let’s do a join in a LINQ query.

Say we have a Product class that looks like:

public class Product
{
    public int ProductId;
    public string Name;
}

and an Inventory class that looks like:

public class Inventory
{
    public int InventoryId;
    public int ProductId;
    public int NumberOnHand;
}

now take a look at this:

List<Inventory> InventoryList = new List<Inventory>();
InventoryList.Add(new Inventory { ProductId = 1, NumberOnHand = 3 });
InventoryList.Add(new Inventory { ProductId = 2, NumberOnHand = 0 });

List<Product> ProductList = new List<Product>();
ProductList.Add(new Product { ProductId = 1, Name = “Apple” });
ProductList.Add(new Product { ProductId = 2, Name = “Orange” });
  
var ProductsInInventory = from InventoryItem in InventoryList
                 join ProductItem in ProductList
                                   on InventoryItem.ProductId equals ProductItem.ProductId
                 where InventoryItem.NumberOnHand > 0
                 select new { ProductItem.Name, InventoryItem.NumberOnHand };

But Brian, that’s not how it’s done in SQL.  

Well, really inner voice?  Like I’ve never done SQL before.

Wasn’t the medication supposed to stop you from hearing me?  ‘Cause it doesn’t seem to be doing a great job.

Oh that?  Stopped takin’ it.  Figured without my inner voice I was just talking to myself.

But yes, Inner Voice is correct, that is not how it’s done.  But I did say that LINQ was like sql, not exactly sql.

The way a join is done is a bit different then in SQL when doing an implicit inner join but it is still pretty simple and actually very similar to doing an explicit inner join.  We just get so used to doing implicit joins we forget about explicit joins.  Once again, things are done in LINQ to facilitate the compiler optimizations and Intelli-sense.

Well, now your foot is wet and I’ve taken you a bit further into the world of LINQ.  Next on the topic is more on lambda expressions and other types of joins.

Later ‘yall,
Brian (and his inner voice)