Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using CreateSourceQuery in CTP4 Code First

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();
    }
like image 220
Adam Rackis Avatar asked Aug 03 '10 16:08

Adam Rackis


2 Answers

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

like image 131
Rowan Miller Avatar answered Sep 27 '22 16:09

Rowan Miller


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

like image 32
zeeshanhirani Avatar answered Sep 27 '22 18:09

zeeshanhirani