Eager load all properties with Entity Framework - entity-framework

So LazyLoadingEnabled = false apparently doesn't do what I thought it did. And has next to no documentation.
I design my entity trees very carefully. I think hard about every relationship. When I load up an entity that is an Aggregate Root, I want everything that is navigable to from there to load. That's how the Aggregate root concept works after all, if I want something to be weekly related I'd stick an id on the entity and manage the relationship myself.
Is there a way to do this with Entity Framework?

You can try obtaining all NavigationProperties of the element type (in IQueryable). By accessing to the MetadataWorkspace of ObjectContext you can get those properties of a specific type and Include all easily.
Note that I suppose you're using EF5 or later version, whereas DbContext is used. We access to ObjectContext via the interface IObjectContextAdapter. Here is the code:
public static IQueryable<T> LoadAllRelatedObjects<T>(this IQueryable<T> source, DbContext context) {
//obtain the EntityType corresponding to the ElementType first
//then we can get all NavigationProperties of the ElementType
var items = (ObjectItemCollection) ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
var entityType = items.OfType<EntityType>().Single(e => items.GetClrType(e) == source.ElementType);
return entityType.NavigationProperties
.Aggregate(source, (c, e) => c.Include(e.Name));
}
Note in new version (since EF5), the namespace of ObjectItemCollection (and other metadata items) is System.Data.Entity.Core.Metadata.Edm, while in the old version (before EF5), it's System.Data.Metadata.Emd.
Usage:
yourModel.YourEntities.LoadAllRelatedObjects(yourModel)
//. more query here ...
;

I don't think there's a way to have all of the navigation properties auto-eager load. How about making an extension method instead?
public static IQueryable<Company> LoadCompany(this IQueryable<Company> query) {
return query.Include(x => x.Divisions)
.Include(x => x.Departments)
.Include(x => x.Employees);
}
Example:
var query = from company in db.Companies.LoadCompany()
where company.Name == "Microsoft"
select company;

Related

How to support OData query syntax but return non-Edm models

Exposing my EF models to an API always seemed wrong. I'd like my API to return a custom entity model to the caller but use EF on the back.
So I may have PersonRestEntity and a controller for CRUD ops against that and a Person EF code-first entity behind in and map values.
When I do this, I can no longer use the following to allow ~/people?$top=10 etc. in the URL
[EnableQuery]
public IQueryable<Person> Get(ODataQueryOptions<Person> query) { ... }
Because that exposes Person which is private DB implementation.
How can I have my cake and eat it?
I found a way. The trick is not to just return the IQueryable from the controller, because you need to materialise the query first. This doesn't mean materialising the whole set into RAM, the query is still run at the database, but by explicitly applying the query and materialising the results you can return mapped entities thereafter.
Define this action, specifying the DbSet entity type:
public async Task<HttpResponseMessage> Get(ODataQueryOptions<Person> oDataQuery)
And then apply the query manually to the DbSet<Person> like so:
var queryable = oDataQuery.ApplyTo(queryableDbSet);
Then use the following to run the query and turn the results into the collection of entities you publicly expose:
var list = await queryable.ToListAsync(cancellationToken);
return list
.OfType<Person>()
.Select(p => MyEntityMapper.MapToRestEntity(p));
Then you can return the list in an HttpResponseMessage as normal.
That's it, though obviously where the property names between the entities don't match or are absent on either class, there's going to be some issues, so its probably best to ensure the properties you want to include in query options are named the same in both entities.
Else, I guess you could choose to not support filters and just allow $top and $skip and impose a default order yourself. This can be achieved like so, making sure to order the queryable first, then skip, then top. Something like:
IQueryable queryable = people
.GetQueryable(operationContext)
.OrderBy(r => r.Name);
if (oDataQuery.Skip != null)
queryable = oDataQuery.Skip.ApplyTo(queryable, new System.Web.OData.Query.ODataQuerySettings());
if (oDataQuery.Top != null)
queryable = oDataQuery.Top.ApplyTo(queryable, new System.Web.OData.Query.ODataQuerySettings());
var list = await queryable.ToListAsync(operationContext.CreateToken());
return list
.OfType<Person>()
.Select(i => this.BuildPersonEntity(i));
More information:
If you simply use the non-generic ODataQueryOptions you get
Cannot create an EDM model as the action 'Get' on controller 'People'
has a return type 'System.Net.Http.HttpResponseMessage' that does not
implement IEnumerable
And other errors occur under different circumstances.

Updating multiple records using Entity Framework

I am using Entity Framework 5. I am looking for a better approach to update multiple records.
People are talking about EF Extensions. But I am not sure how to use it with my scenario.
This is my method signature.
internal void Update( List<Models.StockItem> stockItemsUpdate)
I need to update all the corresponding stockitem entities.
using (var context = new eCommerceEntities())
{
var items = context.StockItems.Where(si => stockItemsUpdate.Select(it => it.ID).Contains(si.ID));
}
I believe above query will return those entities.
How can I use EF extensions in this scenario?
Thanks.
In EntityFramework.Extended's BatchExtensions there is an Update extension method with this signature:
public static int Update<TEntity>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TEntity>> updateExpression)
You can use this as follows:
items.Update(item => new StockItem { Stock = 0 });
to set the stock of the selected items to 0.

EF Filtering a Child Table with Lazy Load

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.
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();
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.
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);
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();
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

Problem using Include() when serializing Entity Framework 4 POCO classes with WCF

I have a WCF service with an Entity Framework 4 model, using POCO classes that are serialized and sent over to client applications. I have LazyLoadingEnabled and ProxyCreationEnabled set to false, and I'm using Linq to Entites to query an Entity, and return it via List<> to the client. Everything goes perfect when I don't use Include():
public List<TBLTable1> GetTBLTable1(string pCode)
{
using (PcFactoryEntities oPcFactoryDB = new PcFactoryEntities())
{
oPcFactoryDB.ContextOptions.ProxyCreationEnabled = false;
oPcFactoryDB.ContextOptions.LazyLoadingEnabled = false;
var oRS = oPcFactoryDB.TBLTable1
.Where(c => c.Code == pCode).ToList();
XmlObjectSerializer serializer = new DataContractSerializer(typeof(TBLTable1));
serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }, oRS[0]);
return oRS;
}
}
After the Linq query, I use the serializer to simulate the serialization process that happens when the POCO class is sent to the client, and I works great. However, when I add an Include() to load one of the navigation list for the class, it starts serializing all of Table2's navigation's list as if LazyLoadingEnabled was set to true, and it goes on forever serializing probably the whole database!
public List<TBLTable1> GetTBLTable1(string pCode)
{
using (PcFactoryEntities oPcFactoryDB = new PcFactoryEntities())
{
oPcFactoryDB.ContextOptions.ProxyCreationEnabled = false;
oPcFactoryDB.ContextOptions.LazyLoadingEnabled = false;
var oRS = oPcFactoryDB.TBLTable1
.Include("TBLTable2")
.Where(c => c.Code == pCode).ToList();
XmlObjectSerializer serializer = new DataContractSerializer(typeof(TBLTable1));
serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }, oRS[0]);
return oRS;
}
}
Why is this happening? Shouldn't the LazyLoadingEnabled set to false apply to the class included manually and return all of it's navigation lists to null as it happens with all of the other navigation lists for Table1? Is there a way to fix this so I can return with Table1 some navigations lists filled in with their navigation lists set to null?
Tks
Instead of trying to directly serialize the entity, try projecting to a DTO and serializing that. I agree what your seeing is bizarre behaviour - but it could be that the EF internal graph is taking over when your serializing the entities, but if you serialize a DTO, EF should not intervene.
E.g:
var dto = oPcFactoryDB.TBLTable1
.Where(x => x.Code == pCode)
.Select(x => new SpecialisedDTO
{
PropertyOne = x,
PropertyTwo = x.TBLTable2
}).ToList();
And then serialize that.
Since your projecting, you don't need to eager load - EF will grab what it needs to based on the query you have provided.
It's usually good practice in N-Tier situations to transmit DTO's over the wire, rather than the pure POCO entities.
Do you have a Navigation Property on TBLtable1 to TBLtable2? The .Include() is used to include entities that are linked va FK relationships and the .Include() is passed the Name of the Navigation Property.
So if you have a Person Entity with a NavigationProperty to an Addresses Entity called PersonAddresses you would then execute the following in order to get the Person and their addresses.
var p = dbContext.Person
.Where(x => x.Id == id)
.Include("PersonAddresses")
.SelectFirstOrDefault;

Entity Framework Delete All on Submit

In LINQ to SQL, I could do:
context.User_Roles.DeleteAllOnSubmit(context.User_Roles.Where(ur => ur.UserId == user.UserId));
Whats the equivalent to this for entity framework?
foreach(var entity in context.User_Roles.Where(ur => ur.UserId == user.UserId))
{
context.User_Roles.DeleteObject(entity);
}
context.SaveChanges();
Of course, you can write an extension method, which would encapsulate this.
This would be something like this:
public static void DeleteObjects<TEntity> (this ObjectSet<TEntity> set, IEnumerable<TEntity> data) where TEntity : class
{
foreach(var entity in data)
set.DeleteObject(entity);
}
Called like:
context.User_Roles.DeleteObjects(context.User_Roles.Where(ur => ur.UserId == user.UserId))
context.SaveChanges();
#Femaref has the right idea, but for a true analog to L2E's DeleteAllOnSubmit, you'll want your extension method to make a copy of the entities being deleted before enumerating so that you don't get "collection modified while enumerating" exceptions.
public static void DeleteAllObjects<TEntity>(this ObjectSet<TEntity> set, IEnumerable<TEntity> data) where TEntity : class {
foreach(var entity in data.ToList()) //data.ToList() makes a copy of data for safe enumeration
set.DeleteObject(entity);
}
foreach(var entity in context.User_Roles.Where(ur => ur.UserId == user.UserId))
{
context.User_Roles.DeleteObject(entity);
}
context.SaveChanges();
of course, this solution can work. But, it is the most inefficient solution.
This solution will generate one delete SQL command for each record (entity).
Imaging that you want to delete all data before year 2000 . there are more than 1,000,000 records in the database. If delete these objects in this way, more than 1,000,000 SQL commands will be sent to the server, it is a unnecessary big waste.
What
There is no RemoveAll equivalent in Entity Framework, so you can load entities in memory and remove them one by one using DeleteObject method.
You can use Linq : context.MyEntitie.RemoveAll(context.MyEntitie);
use EntityFramework.Extensions
1) First install EntityFramework.Extensions using NuGet
2) Here is the code similar to Linq2Sql's DeleteAllOnSubmit():
using EntityFramework.Extensions;
....
public void DeleteAllUsers(User_Role user){
context.User_Roles.Delete(ur => ur.UserId == user.UserId);
context.SaveChanges();
}
...