views:

220

answers:

2

I'm guessing this is impossible, but I'll throw it out there anyway. Is it possible to use CreateSourceQuery when programming with the EF4 CodeFirst API, in CTP4? I'd like to eagerly load properties attached to a collection of properties, like this:

var sourceQuery = this.CurrentInvoice.PropertyInvoices.CreateSourceQuery();
sourceQuery.Include("Property").ToList();

But of course CreateSourceQuery is defined on EntityCollection<T>, whereas CodeFirst uses plain old ICollection (obviously). Is there some way to convert?

I've gotten the below to work, but it's not quite what I'm looking for. Anyone know how to go from what's below to what's above (code below is from a class that inherits DbContext)?

ObjectSet<Person> OSPeople = base.ObjectContext.CreateObjectSet<Person>();
OSPeople.Include(Pinner => Pinner.Books).ToList();

Thanks!

EDIT: here's my version of the solution posted by zeeshanhirani - who's book by the way is amazing!

dynamic result;

if (invoice.PropertyInvoices is EntityCollection<PropertyInvoice>) 
   result = (invoices.PropertyInvoices as EntityCollection<PropertyInvoice>).CreateSourceQuery().Yadda.Yadda.Yadda 
else 
   //must be a unit test! 
   result = invoices.PropertyInvoices; 

return result.ToList();

EDIT2:

Ok, I just realized that you can't dispatch extension methods whilst using dynamic. So I guess we're not quite as dynamic as Ruby, but the example above is easily modifiable to comport with this restriction

EDIT3:

As mentioned in zeeshanhirani's blog post, this only works if (and only if) you have change-enabled proxies, which will get created if all of your properties are declared virtual. Here's another version of what the method might look like to use CreateSourceQuery with POCOs

public class Person {
    public virtual int ID { get; set; }
    public virtual string FName { get; set; }
    public virtual string LName { get; set; }
    public virtual double Weight { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

public class Book {
    public virtual int ID { get; set; }
    public virtual string Title { get; set; }
    public virtual int Pages { get; set; }
    public virtual int OwnerID { get; set; }
    public virtual ICollection<Genre> Genres { get; set; }
    public virtual Person Owner { get; set; }
}

public class Genre {
    public virtual int ID { get; set; }
    public virtual string Name { get; set; }
    public virtual Genre ParentGenre { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

public class BookContext : DbContext {
    public void PrimeBooksCollectionToIncludeGenres(Person P) {
        if (P.Books is EntityCollection<Book>)
            (P.Books as EntityCollection<Book>).CreateSourceQuery().Include(b => b.Genres).ToList();
    }
+2  A: 

It is definately possible to do so. If you have marked you collection property with virtual keyword, then at runtime, you actual concrete type for ICollection would be EntityCollection which supports CreateSourceQuery and all the goodies that comes with the default code generator. Here is how i would do it.

public class Invoice { public virtual ICollection PropertyInvoices{get;set} }

dynamic invoice = this.Invoice; dynamic invoice = invoice.PropertyInvoices.CreateSourceQuery().Include("Property");

I wrote a blog post on something similar. Just be aware that it is not a good practice to rely on the inner implementation of ICollection getting converted to EntityCollection. below is the blog post you might find useful

http://weblogs.asp.net/zeeshanhirani/archive/2010/03/24/registering-with-associationchanged-event-on-poco-with-change-tracking-proxy.aspx

zeeshanhirani
Aha - genius! I would do it like so (inside of the DAO method, or wherever your building the query)dynamic result;if (invoice.PropertyInvoices is EntityCollection<PropertyInvoice>) result = (invoices.PropertyInvoices as EntityCollection<PropertyInvoice>).CreateSourceQuery().Yadda.Yadda.Yaddaelse //must be a unit test! result = invoices.PropertyInvoices;return result.ToList();
Adam
Yuck - lemme re-write that as an answer an get better formatting.Hey, on another note - I *LOVE* your book. I just finished it - brilliant! Well done.
Adam
Thanks. Now you owe me a review on amazon. Not that it helps me make money in the book other than Apress making money, it would just help us in writing the second version of the book.
zeeshanhirani
I already did :) Juts 2 more 5-star reviews to go to get an average of 5 stars. Maybe I was just angry with how bad the previous Apress EF4 book was ("Pro" my a$$!) but I really enjoyed yours.As the review said, you might consider copy pasting some of the common setup steps into a single location to save pages, but hey, if you did that, other readers would complain about constant flipping!
Adam
+1  A: 

Hey,

It is possible to add a method to you derived context that creates a source query for a given navigation on an entity instance. To do this you need to make use of the underlying ObjectContext which includes a relationship manager which exposes underlying entity collections/references for each navigation:

public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
    var ose = this.ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
    var rm = this.ObjectContext.ObjectStateManager.GetRelationshipManager(entity);

    var entityType = (EntityType)ose.EntitySet.ElementType;
    var navigation = entityType.NavigationProperties[navigationProperty];

    var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);

    return ((dynamic)relatedEnd).CreateSourceQuery();
}

You could get fancy and accept a Func for the navigation property to avoid having to specify the T, but here is how the above function is used:

using (var ctx = new ProductCatalog())
{
    var food = ctx.Categories.Find("FOOD");
    var foodsCount = ctx.CreateNavigationSourceQuery<Product>(food, "Products").Count();
}

Hope this helps!

~Rowan

Rowan Miller
Wow - way cool - thanks Rowan, and welcome to StackOverflow.
Adam