3

I'm using entity framework with POCOs and the repository pattern and am wondering if there is any way to filter a child list lazy load. Example:

class Person
{
    public virtual Organisation organisation {set; get;}
}
class Organisation
{
    public virtual ICollection<Product> products {set; get;}
}
class Product
{
    public bool active {set; get;}
}

Currently I only have a person repository because I'm always starting from that point, so ideally I would like to do the following:

Person person = personRepo.GetById(Id);
var products = person.organisation.products;

And have it only load products where active = true from the database.

Is this possible and if so how?

EDIT My best guess would be either a filter can be added to the configuration of the entity. Or there might be a way to intercept/override the lazy load call and modify it. Obviously if I created an Organisation Repository I could manually load it as I please but I am trying to avoid that.

Dale K
  • 25,246
  • 15
  • 42
  • 71

5 Answers5

2

There's not a direct way to do this via lazy loading, but if you were willing to explicitly load the collection, you could follow whats in this blog, see the Applying filters when explicitly loading related entities section.

context.Entry(person)
    .Collection(p => p.organisation.products)
    .Query()
    .Where(u => u.IsActive)
    .Load();
Mark Oreta
  • 10,346
  • 1
  • 33
  • 36
  • Yes I saw that, and something like that is a possible fall back solution. As I'm using the repository pattern I would want to do anything of this nature in the repository, and that would therefore mean explicitly loading the organisation as well. – Dale K Aug 30 '12 at 01:56
1

This might be related:

Using CreateSourceQuery in CTP4 Code First

If you were to redefine your properties as ICollection<T> rather than IList<T> and enable change-tracking proxies, then you might be able to cast them to EntityCollection<T> and then call CreateSourceQuery() which would allow you to execute LINQ to Entities queries against them.

Example:

var productsCollection = (EntityCollection<Product>)person.organisation.products;
var productsQuery = productsCollection.CreateSourceQuery();
var activeProducts = products.Where(p => p.Active);
Community
  • 1
  • 1
luksan
  • 7,661
  • 3
  • 36
  • 38
  • Yeah thats similar to @Mark Oreta's suggestion, again my main concern is being able to manage it all inside the repository. And using this method requires that I load the organisation immediately on loading a person to be able to load the products explicitly. – Dale K Aug 30 '12 at 02:01
1

You can do what Mark Oreta and luksan suggest while keeping all the query logic within the repository.

All you have to do is pass a Lazy<ICollection<Product>> into the organization constructor, and use the logic they provided. It will not evaluate until you access the value property of the lazy instance.

UPDATE

/*  
    First, here are your changes to the Organisation class: 
    Add a constructor dependency on the delegate to load the products to your 
    organization class.  You will create this object in the repository method 
    and assign it to the Person.Organization property 
*/
public class Organisation
{
    private readonly Lazy<ICollection<Product>> lazyProducts;
    public Organisation(Func<ICollection<Product>> loadProducts){
        this.lazyProducts = new Lazy<ICollection<Product>>(loadProducts);
    }

    //  The underlying lazy field will not invoke the load delegate until this property is accessed
    public virtual ICollection<Product> Products { get { return this.lazyProducts.Value; } }
}

Now, in your repository method, when you construct the Person object you will assign the Organisation property with an Organisation object containing the lazy loading field.

So, without seeing your whole model, it will looks something like

 public Person GetById(int id){
    var person = context.People.Single(p => p.Id == id);

    /*  Now, I'm not sure about the cardinality of the person-organization or organisation
        product relationships, but let's assume you have some way to access the PK of the 
        organization record from the Person and that the Product has a reference to 
        its Organisation.  I may be misinterpreting your model, but hopefully you 
        will get the idea
     */

     var organisationId = /*  insert the aforementioned magic here */

     Func<ICollection<Product>> loadProducts = () => context.Products.Where(product => product.IsActive && product.OrganisationId == organisationId).ToList(); 

    person.Organisation = new Organisation( loadProducts );

    return person;
 }

By using this approach, the query for the products will not be loaded until you access the Products property on the Organisationinstance, and you can keep all your logic in the repository. There's a good chance that I made incorrect assumptions about your model (as the sample code is quite incomplete), but I think there is enough here for you to see how to use the pattern. Let me know if any of this is unclear.

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • Unless I'm misunderstanding you that still requires I manually construct/load the organisation when loading a person? And I'm not quite sure how to combine your code and their code so that it works correctly? Thanks – Dale K Sep 05 '12 at 20:43
  • You are misunderstanding. `Lazy` accepts a delegate `Func` parameter, which is only evaluated once (the first time its `Value` property is called). I will post a sample, but to make sure that I don't send you down the wrong path, I need to know, **What is the lifecycle scope of your repository and underlying ObjectContext?** – smartcaveman Sep 06 '12 at 13:03
  • Its an ASP.NET MVC application, my service classes are injected with repositories, so both repositories and data context objects last the duration of the request. Thanks SmartCaveman - your advice is much appreciated. – Dale K Sep 06 '12 at 20:34
  • @DaleBurrell, I posted some sample code to help communicate what I was trying to explain. I had to make some assumptions about the underlying model to give a full enough example. If this is still unclear you may need to post some more code/schema info – smartcaveman Sep 07 '12 at 03:06
  • Thanks for that, certainly makes sense, although I think my first comment does actually still stand because this *does* require that I *manually* load the organisation within the person as opposed to the organisation being lazy loaded by EF on first access. I was sort of hoping that there might be an event one could handle when EF came to do a lazy load which one could intercept and perform differently. Still very useful information, so thanks again. – Dale K Sep 07 '12 at 03:47
  • @DaleBurrell, if by *manually* you mean it requires code, and not just configuration, then you are correct. However, whether EF or your app code does this, the same process will be taking place. It would be possible to modify the EF code generation to cater more to your specifications. As a side note, NHibernate gives you more configuration options for specifying automated lazy loading and for intercepting object construction, because it generates derived classes that override the property accessors, so it might be a better fit for your paradigm. – smartcaveman Sep 07 '12 at 12:40
  • Believe me I have wondered on many occasions whether EF was the way to go over NHibernate. The one thing that bugs me big time is the inability to have a entity field populated by database function, whereas in NHibernate thats a piece of cake. Still it good to know all these different solutions. – Dale K Sep 08 '12 at 01:56
  • @DaleBurrell, NHibernate is definitely the more powerful ORM, but it also has a steeper learning curve and the options for visual designers aren't as good. – smartcaveman Sep 08 '12 at 04:41
  • Now you tell me :) I've used NHibernate before, but for some strange reason felt more comfortable sticking with MS products... I didn't find it any harder to learn than EF to be honest, in fact I think a bit easier. And after my first import of my database into EF I make all the changes by manually editing the XML file - thats how I roll - so I don't care about visual designers :) anyway it will be good to be familiar with both so I can choose for future products. Thanks for you comments, great to get your useful ideas. – Dale K Sep 08 '12 at 10:14
0

You could possibly use Query() method to achieve this. Something like:

 context.Entry(person)
            .Collection(p => p.organisation.products)
            .Query()
            .Where(pro=> pro.Active==true)
            .Load();

Have a look at this page click here

Dale K
  • 25,246
  • 15
  • 42
  • 71
Bishnu Paudel
  • 2,083
  • 1
  • 21
  • 39
  • 2
    That will cause the product list to be loaded first and then filtered in memory since it is a LINQ to Objects query (because `List` implements `IEnumerable` but not `IQueryable`). I think the asker is wanting to know how to only retrieve the entities matching the criteria from the database. – luksan Aug 30 '12 at 01:06
0

Is your repository using something like:

IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)

If so you can do something like this:

var person = personRepo.Find(p => p.organisation.products.Any(e => e.active)).FirstOrDefault();
  • I'm not sure I understand that correctly, but it looks to me like its going to load the first person with a related active product? As opposed to when loading a specific person, lazy loading the related organisation, then loading the filtered products. – Dale K Aug 30 '12 at 02:03