Auto Access to non-Indexed Collections : Or How I Learned to Stop Worrying and Love the VB Compiler

Auto Access to non-Indexed Collections : Or How I Learned to Stop Worrying and Love the VB Compiler

  • Comments 5

I just finished a couple more How Do I videos on using EF with WPF using VS2008 SP1 (they’ll be published soon) and while I was translating my VB code to C# I stumbled upon an error in C# that does not happen in VB. This of course got me curious and so I thought I’d share what I found out.

I have an EDM defined with a parent entity Order and child OrderDetails that I created with the designer – very basic – like I show in this video. In the example I was working on, I was putting the results of an order query into a generic list but also including the OrderDetails so I end up with an EntityCollection called OrderDetails on every Order.

OMSEntities db = new OMSEntities();

var results = from o in db.Orders.Include("OrderDetails")
              select o;

List<Order> orderList = new List<Order>(results);

However if I want to get the first Order’s first OrderDetail, I get an error in C#: “Cannot apply indexing with [] to an expression of type 'System.Data.Objects.DataClasses.EntityCollection<EFTestDAL.OrderDetail>'” when I write this line:

//ERROR
Console.WriteLine(orderList[0].OrderDetails[0].OrderDetailID);

However, the VB code works perfectly.

Dim db As New OMSEntities

Dim results = From o In db.Orders.Include("OrderDetails") _
              Select o

Dim orderList As New List(Of Order)(results)

'No Error
Console.WriteLine(orderList(0).OrderDetails(0).OrderDetailID)

So what gives in C#? At first I thought it was something with the way the EntityCollection was being generated in each of the designer code-behind for the languages, maybe I found a bug? So I created a single solution and used C# as the data access layer project and then added a C# client and a VB client project and still the same issue. VB worked fine against the C# defined EntityCollection. Hmmmm……

Next Stop, BING. I searched for the error message and no luck. Okay well it helps to actually read these error messages so let’s put on my glasses and break out my Microsoft Compiler Error Message Magic Translator* and yeah, as the title of this post suggests, the EntityCollection doesn’t actually implement an indexer. Okay so again, why does it work in VB?

The VB Compiler loves to help. It’s like uber-helpful (sometimes to its own fault, I admit), but this time it’s really helping in a good way. The way to get this to work in C# is to call the ElementAt or ElementAtOrDefault extension methods. So in C# to get the OrderDetail by index you write:

//No ERROR
Console.WriteLine(orderList[0].OrderDetails.ElementAt(0).OrderDetailID);

So what Visual Basic is doing for you is exactly that. Actually it’s calling ElementAtOrDefault for you if the collection does not implement an indexer of its own. So you can write the same syntax for all collections. This help topic on the Extension Indexer Property gave us the clue we needed. So, if an IEnumerable or IQueryable doesn't define an indexer property, VB will translate the indexer syntax to the ElementAtOrDefault operator. Good to know!

So I asked Diego on the EF team why the EntityCollection doesn’t implement an indexer. I was told that the EF team decided long time ago that since there was conceptually no guarantee on the ordering of EntityCollections, they didn’t want to give the illusion that such ordering existed by exposing an indexer. The internal implementation of EntityCollection(Of T) uses HashSet(Of T), which is super-fast and doesn’t have an indexer.

Enjoy!

*No there is no such thing as a Microsoft Compiler Error Message Magic Translator that I know of so please don’t ask! But please feel free to create one on CodePlex. ;-)

Leave a Comment
  • Please add 2 and 5 and type the answer here:
  • Post
  • You are right!

    your OrderDetails class doesn't actually implement an indexer.

    My following code for you information , I implement the indexer:

    public string this[int index]

    {

    get{

    if(index>=arrItems.Length)

    throw new Exception("The index is over arrItems's length");

    else

    return arrItems[index];

    }

    }

    public static void Main()

    {

    MyIndex myIndex=new MyIndex();

    Console.WriteLine(myIndex[2]);

    }

  • Thanks Peter,

    But the EntityCollection is a Entity Framework type. See my last paragraph on why the EF team chose not to implement the indexer.

    Cheers,

    -B

  • I think VB is mistaken in this case and the EF team is correct. The collection has no order guarantee and should not implement indexing.

    There is never an entity order guarantee from a query (even OrderBy can take you only so far).

    Indexing is misleading and bug-producing. VB is overreaching in this case and I prefer C# asking you to be explicit in your mistake.

    Of course both languages support "First" ... and no one can say what that will return.

    Btw, why DID you index by 0? Isn't "First" more intentional?

  • Hey Ward,

    I disagree ;-), I like what VB is doing here as the syntax follows suit with how array indexers work. So I could swap data sources easily.

    I was actually doing some processing in a For i = 0 to count type of loop. I wasn't concerned with the order of the items in the collection at all.

    And thanks for (finally) finding some interest in VB ;-)

    Cheers,

    -B

  • Thanks so much for posting this! Was wondering the same.

Page 1 of 1 (5 items)