I have a method that calls a DbSet from an Entity Framework database:
public static List<CostEntryVM> ToViewModelList(this DbSet<CostEntry> CostEntrys, Expression<Func<CostEntry, bool>> query) {
return AutoMapper.Mapper.Map<List<CostEntry>, List<CostEntryVM>>(
CostEntrys
.Include(x => x.Job)
.Include(x => x.User)
.Where(query)
.ToList());
}
To use this I can then do for example:
CostEntrys.ToViewModelList(x => x.Active == true);
I want to also be able to call:
CostEntrys.ToViewModelList(x => x.Include(y => y.Job).Include(y.User), x => x.Active == true);
I can't for the life of me figure out how the method signature should look or how I would then apply that to the DbSet.
How can I do this?
First you need to change the extension method to:
public static List<CostEntryVM> ToViewModelList(
this DbSet<CostEntry> CostEntrys,
Expression<Func<CostEntry, bool>> query,
Func<IQueryable<CostEntry>, IQueryable<CostEntry>> func)
{
// Adding the predicate query
IQueryable<CostEntry> queryable = CostEntrys.Where(query);
// Adding include paths
IQueryable<CostEntry> queryableWithFetch = func(queryable);
// Executing the query and map it to the view model object
return AutoMapper.Mapper.Map<List<CostEntry>, List<CostEntryVM>>(
queryableWithFetch.ToList());
}
And then you can call it:
CostEntrys.ToViewModelList(
x => x.Active == true,
x => x.Include(y => y.Job).Include(y.User));
Related
I am using inner join to return results with Entity Framework (v6.2.0) and the following code is not returning the RouteWaypoints children (i.e. route.RouteWaypoints is always null). Interestingly, single children are loading (Customer, OriginLocation, etc), but not multiple children:
public List<Route> GetAllForTripWithWaypoints(int tripId)
{
return (
from route in GetAllBaseWithWaypoints()
from tripTask in DbContext.TripTasks.Where(x =>
x.TripId == tripId && x.OriginLocationId == route.OriginLocationId)
select route
).ToList();
}
private IQueryable<Route> GetAllBaseWithWaypoints()
{
return DbContext.Routes
.Include(x => x.Customer)
.Include(x => x.OriginLocation)
.Include(x => x.DestinationLocation)
.Include(x => x.RouteWaypoints.Select(y => y.Location))
.OrderBy(x => x.OriginLocation.Name).ThenBy(x => x.DestinationLocation.Name)
.AsQueryable();
}
This approach does work if I load just the Route entity, but not when I do the join. As a reference, this does load the children successfully:
public Route GetByIdWithWaypoints(int id, bool validateExists = true)
{
var route = GetAllBaseWithWaypoints().FirstOrDefault(x => x.Id == id);
if (validateExists && route == null)
throw new Exception("Route not found for id: " + id);
return route;
}
How can I keep it working when joining?
I did an imperfect workaround by making two calls to the db - slightly less efficient, but it solves the problem:
public List<Route> GetAllForTripWithWaypoints(int tripId)
{
var routeIds = (
from route in GetAllBase()
from tripTask in DbContext.TripTasks.Where(x =>
x.TripId == tripId && x.OriginLocationId == route.OriginLocationId)
select route.Id
).ToList();
return GetAllBaseWithWaypoints().Where(x => routeIds.Contains(x.Id)).ToList();
}
I have the following method in data access layer:
GetByFilterIncluding(Expression<Func<TEntity, bool>> filter, params Expression<Func<TEntity, object>>[] includeProperties)
Now I have to call it from business layer, including properties:
dll.GetByFilterIncluding(x => x.Id == id, x => x.Person, x => x.Address)
However, I want to call it like this:
if (needPerson) {
includeProperties.Add((x => x.Person));
}
if (needAddress) {
includeProperties = (x => x.Address);
}
dll.GetByFilterIncluding(x => x.Id == id, includeProperties)
I simply do not understand how to define includeProperties array/list?
EDIT:
As a note - I do not have access to Data access layer and I cannot change it.
I have a mapping:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Foo, FooDto>()
.ForMember(dest => dest.Id,
opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Name,
opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.PhoneNumber,
opt => opt.MapFrom(src => src.PhoneNumbers.Number)) //etc.
});
and I'm trying to use it in a unit test calling into some mocked up EF objects:
var ids = new List<string>()
{
"123";
"456";
"789";
};
var data = new List<Foo>();
foreach(var id in ids)
{
data.Add(new Foo() { Id = id });
}
this.mockContext = new Mock<entities>();
this.mockSet = new Mock<DbSet<Foo>>();
this.mockSet.As<IQueryable<Foo>>().Setup(p => p.Provider).Returns(data.Provider);
this.mockSet.As<IQueryable<Foo>>().Setup(p => p.Expression).Returns(data.Expression);
this.mockSet.As<IQueryable<Foo>>().Setup(p => p.ElementType).Returns(data.ElementType);
this.mockSet.As<IQueryable<Foo>>().Setup(p => p.GetEnumerator()).Returns(data.GetEnumerator());
When I query the entities directly:
var id = "123";
var bar = this.mockContext.Object.Foo.Where(p => p.id == id);
I get back an IQueryable() with a single result, as expected. But when I try to project my object into a DTO:
var id = "123";
var buzz = this.mockContext.Object.Foo.Where(p => p.id == id).ProjectTo<FooDto>(this.config);
The IQueryable I get back throws a Null Reference Exception if I try to access the results in any way. So for example:
buzz.ToList();
buzz.SingleOrDefault(); // This mirrors the actual call in my code since this is a GetById query.
both fail. What am I missing here?
The problem lies is that Foo uses EF navigation properties to refer to other objects. Specifically a PhoneNumber in this instance. Since the test data is created without the linked PhoneNumber object, it breaks inside of the ProjectTo method. This isn't a problem when grabbing the top level Queryable directly, but Automapper needs the objects to exist (even if they're empty) in order to complete the mapping. Changing that line to:
data.Add(new Foo() { Id = id, PhoneNumber = new PhoneNumber() });
allows the ProjectTo method to complete, albeit with null values.
I have the following Repository method:-
public AccountDefinition GetCustomer2(int id)
{
var c = entities.AccountDefinitions
.Where(p=>p.ORG_ID==id)
.Include(a => a.SDOrganization)
.Include(a2 => a2.SiteDefinitions)
.Include(a3 => a3.SDOrganization.AaaPostalAddresses)
.Include(a4 => a4.SiteDefinitions.SelectMany
(a5 => a5.DepartmentDefinitions.SelectMany
(a6 => a6.SDUsers.Select
(a7 => a7.AaaUser))))
.SingleOrDefault();
return c;
}
The the following action method which calls the above method:-
public ActionResult Details2(int id = 0)
{
AccountDefinition cd = repository.GetCustomer2(id);
return View("copy",cd);
}
but when i navigate to the Action Method , i get the following error on the repository class:-
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties.
So what is wrong with my code?
I think you may want to do something like
public AccountDefinition GetCustomer2(int id)
{
var c = entities.AccountDefinitions.Where(p=>p.ORG_ID==id)
.Include(a => a.SDOrganization)
.Include(a2 => a2.SiteDefinitions)
.Include(a3 => a3.SDOrganization.AaaPostalAddresses)
.Include(a4 => a4.SiteDefinitions.Select(a5 => a5.DepartmentDefinitions.Select(a6 => a6.SDUsers.Select(a7 => a7.AaaUser))));
return c;
}
I'm using EF 4.1 DBContext API, and I'm trying to load a parent entity named User and then explicitly load related Project entities using a .Where(x => x.IsEnabled) filter:
I found a suggested approach here under "Applying filters when explicitly loading related entities". But I cannot figure out why user.Projects is not being populated. Using SQL Profiler, I have verified I'm querying Projects and returning the data. But it's not being loaded into my user object.
Any thoughts?
Here is my code:
public User GetUser(string userName)
{
try
{
using (var context = new Entities())
{
var user = context.Users.FirstOrDefault(x => string.Compare(x.UserName, userName, true) == 0);
context.Entry(user).Collection(x => x.Projects).Query().Where(x => x.IsEnabled).Load();
//TODO: I expect user.Projects.Count() > 0.
}
}
catch (Exception ex)
{
LogWrapper.DumpException(ex);
}
return null;
}
User.projects = context.projects.select(x => x.user).where(x => x.isenabled);
Came across this problem recently, this is what I did. Hope you find it useful.
var query = context.User.Where(x => x.UserName == userName)
.Include(x => x.Projects.All(p => p.IsEnabled == true))
var data = query.ToList();