views:

193

answers:

5

I got the 2 following entity : User and Post

Simple version of user :

public virtual int Id { get; set; }
public virtual IList<Post> Posts { get; set; }

Posts aren't load by default.

In certain condition, I need to load the Posts (for example, I want to count the number of posts for that user). For now, I've add a method call LoadPosts(User user) in UserRepository:

_context.LoadProperty(user, "Posts");
  • Is there a more logically place to put that method ?
  • Should I put it in PostRepository ? Something like CountPosts(int userId) ?
  • Should I offer an overload on my LoadMethod. Example : Load(bool loadPosts)

Is there a way that if I write myUser.Posts.Count() Posts is loaded automatically instead of being null ?

A: 

If you are using the custom data provider, in that case I would suggest you to load the data in CreateDataSource method.

Ram
+4  A: 

The scenario you are describing is called Lazy loading and it is supported by EF4. Judging by your code, you have POCO objects, and since you've declared your list as virtual, EF can create proxies in order to provide lazy loading features. There is a setting you can use to enable Lazy loading: context.ContextOptions.LazyLoadingEnabled = true;. In this case, whenever you access the Posts property of an already loaded User, the data will get loaded from the DB.

More info here, in the "Deferred/Lazy Loading" section: http://blogs.msdn.com/b/adonet/archive/2009/05/28/poco-in-the-entity-framework-part-2-complex-types-deferred-loading-and-explicit-loading.aspx

The reason why this works is because when I marked my collection property as virtual, this allowed the Entity Framework to provide a proxy instance for my POCO type at runtime, and it is this proxy that does automatic deferred loading. The proxy instance is based on a type that derives from my own POCO entity class - so all functionality you have provided is preserved. From a developer point of view, this allows you to write persistence ignorant code even when deferred loading might be a requirement.

And briefly on your mini-question list:

  • Is there a more logically place to put that method ? - If you decide to implement a method to load Posts for a given user yourself, it logically belongs to the UserRepository.
  • Should I put it in PostRepository ? Something like CountPosts(int userId) ? - It might belong to the PostsRepository in case you load posts unrelated to a certain user (e.g. a global keyword search among all posts).
  • Should I offer an overload on my LoadMethod. Example : Load(bool loadPosts) - Yes, you can do that. With EF, however, you just have to turn off Lazy loading. The Posts will get loaded together with the User.
Yakimych
A: 

You could include the Posts entities in your results on a case by case basis by loading the child collection as follows:

var query = from c in context.User.Include("Posts") select c;

I'm not 100% sure that the above example doesn't have a typo. The following blog post explains in greater detail: Eager Loading child entities in Entity Framework

yorkrj
A: 

You should put it in Repository, by Overloading or adding new method. For More

If you are calling "_context.Load()" outside the repository than it may be in-correct.

paragy
A: 

Is there a more logically place to put that method ?

IMO, yes. I wouldn't have a specific method like that unless you require a list of posts a lot of time. I would Eager Load the Posts on demand. (More on this, below)

Should I put it in PostRepository ?

Sorta. LoadPosts(User user) is a PostRepository specific login, IMO. BUT - it's a very specific method .. which means that sooner-or-later you're PostRepository will get very busy with lots of specific methods. We usually have a generic repository which allows Expressions to come in. That way, you don't need to create lots of specific methods. You can have one method that excepts expressions and retrieves your various requests.

for example.

IQueryable<Post> Find(Expression<Func<Post, bool>> predicate, 
                      string[] includeAssociations);

Then you can use it to eager load what you need.

eg. Get all posts for a particular user.

var posts = _postRepostitory
    .Find(x => x.UserId = userId, new [] { "User" } )
    .ToList();

or the flip side, For a user, get me all their posts...

var user = _userRepository
    .Find(x => x.UserId = userId, new [] { "Posts" } )
    .ToList();

If you need some help with the code inside the Find method, just ask .. but it's pretty trivial.

The idea is this (in order, which is cruical)

  • the expression is just applied to the Post entity inside the hidden context inside the postRespository instance.
  • Finally, you Include for each association that is required, last.

Nothing is hardcoded. It's all defined by the coder when they call the Find method :) One method to rule them all.

NOTE: Of course, i actually have 4 - one with no arguments .. all the way down to the one i've shown, with 4 args.

Something like CountPosts(int userId) ?

(Untested, pseduo code .. thinking off the top of my head)

var count = _postRepostiory
    .Find(x => x.UserId = userId, new [] { "Posts" })
    .Select(x => x.Posts).Count;

Should I offer an overload on my LoadMethod. Example : Load(bool loadPosts)

Nope. not needed now.

Is there a way that if I write myUser.Posts.Count() Posts is loaded automatically instead of being null ?

Yep, if you follow my suggestions above.

var user = _userRepository
    .Find(x => x.UserId = userId, new [] { "Posts" })
    .SingleOrDefault();

var postCount = user == null || user.Posts == null ? 0 : user.Posts.Count;

Go nuts and program away, padawan!!

Pure.Krome