I'm working on a solution with Entity Framework Core and AutoMapper (version 9).
The mapping from my entity classes to DTOs is done with projections.
var dto = await context.Companies
.ProjectTo<MyDto>(config, new { departmentName = "Sales" });
In the mapping, I select a certain department.
public class MyProfile : Profile
{
public MyProfile()
{
string departmentName = null;
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(src =>
src.Divisions.SelectMany(dv =>
dv.Departments.Where(d => d.Name == departmentName)
)
.Single().Location));
}
}
Is it possible to move the code to select the department to another (private) method within the profile class?
This code will also be needed in the mapping of other members of the DTO.
I tried to move the code to another method, but then the Department's collection of a company is not populated. Probably because EF can't translate the method to SQL. I also tried to rewrite the method to an expression of Func that returns a department, but then I can't use the result directly to access the department properties in the mapping.
public class MyProfile : Profile
{
public MyProfile()
{
string departmentName = null;
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(src =>
SelectDepartment(src, departmentName).Location)
);
}
private Department SelectDepartment(Company company, string departmentName)
{
var department = company.Divisions
.SelectMany(Departments.Where(d => d.Name == departmentName)
.First();
if (department == null)
throw new AutoMapperMappingException($"Department '{departmentName}' not found!");
return department;
}
}
Any suggestions?
It's a half of a solution for you, but I will post it anyway. You can get the mapping done by creating a method that provides an expression. The problem is - how to throw an exception if the fitting department wasn't found, since you cannot throw one in an expression. What I would suggest is to throw the exception after the mapping since you have to query for the data anyway.
public class MyMappingProfile : Profile
{
public MyMappingProfile()
{
string departmentName = "Foo";
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(SelectDepartment(departmentName)));
}
private Expression<Func<Company, string>> SelectDepartment(string departmentName)
=> (company) => company.Divisions
.SelectMany(division => division.Departments)
.Where(department => department.Name == departmentName)
.FirstOrDefault()
.Location;
}
EF Core will generate nice query:
SELECT(
SELECT TOP(1) [d0].[Name]
FROM [Division] AS[d]
INNER JOIN [Department] AS [d0] ON [d].[Id] = [d0].[DivisionId]
WHERE ([c].[Id] = [d].[CompanyId])
AND ([d0].[Name] = #__departmentName_0))
AS [DepartmentLocation]
FROM [Company] AS[c]
Usage:
var result = dbContext
.Set<Company>()
.ProjectTo<MyDto>(mapperConfiguration)
.ToList();
result.ForEach(myDto =>
{
if (myDto.DepartmentLocation == null)
{
throw new AutoMapperMappingException("Department was not found!");
}
});
Related
I am building an application that consists of only an API using .net core 2.1.
I have a parent class (Dad) which owns only a single child (Kid). I am looking for the most efficient way to format the JSON response of my controller; for developers who will integrate my API to their applications.
public class Dad
{
public long Id{get;set;}
public string Name{get;set;}
public Kid OnlyChild {get;set;}
}
public class Kid
{
public long Id{get;set;}
public string FirstName{get;set;}
public string LastName{get;set;}
public string Useless{get;set;}
}
Currently I am doing something like this in the controller:
[HttpGet("{id}")]
public async Task<IActionResult> GetDad([FromRoute] long id)
{
dynamic DadResponse = _context.Dads
.Where(o => o.Id == id)
.AsNoTracking()
.Select(p => new
{
Dad = p.Name,
Kid = string.Format("{0} {1}", p.Kid.FirstName, p.Kid.LastName)
}).FirstOrDefault();
return Ok(DadResponse);
}
The upside with this approach is that:
the resulting DadResponse object has the format I want for my
API.
the resulting MySQL query generated by EF Core will be
optimized and only select Dad.Name, Kid.FirstName and Kid.LastName.
The downside is that if Kid is null, it will generate an exception.
What is the best way around this; maybe I am using a wrong approach all together. I tried to use JsonIgnore attributes in my models but each of my controllers might need to return slightly different properties (e.g. GET /Kids will return all the kids with their Id, whereas GET /Dads may return only the format described above).
Update:
Ideally I would like to have Kid return null value if Dad doesn't have a Kid, but I cannot do something like this:
Kid = (Kid == null ? null : string.Format("{0} {1}", p.Kid.FirstName, p.Kid.LastName))
I have tried to dynamically update the value after the select, using the following:
dynamic DadResponse = _context.Dads
.Where(o => o.Id == id)
.AsNoTracking()
.Select(p => new
{
Dad = p.Name,
Kid = p.Kid
}).FirstOrDefault();
DadResponse.Kid = (DadResponse.Kid == null ? null : string.Format("{0} {1}", DadResponse.Kid.Firstname, DadResponse.Kid.Lastname);
return Ok(DadResponse);
But that throws another exception.
Your query is an EF query that will be translated to SQL. Null propagation can't be translated which is why you get that error.
You don't have to format the string in the EF query though, you can load the data you want and use another LINQ to Objects query to map them to their final form. If you load only a single object, you can return a new anonymous type:
For example :
var data = _context.Dads
.AsNoTracking()
.Where(o => o.Id == id)
.Select(p => new {
Dad = p.Name,
Kid = new {p.Kid?.FirstName, p.Kid?.LastName}
})
.FirstOrDefault();
var dadResponse = new {
data.Dad,
Kid= $"{data.Kid.FirstName} {data.Kid.LastName}"
};
return Ok(dadResponse);
If you don't want to return the Kid element at all, you can simply omit it from the result:
if (data.Kid.FirstName==null && data.Kid.LastName==null)
{
return Ok(new {Dad=data.Dad});
}
else
{
...
}
Of course, someone could say that since we don't care about the Kid property, we could just return the KidFirstName and KidLastName as separate properties, making the code a bit simpler :
var data = _context.Dads
.AsNoTracking()
.Where(o => o.Id == id)
.Select(p => new {
Dad = p.Name,
KidFirstName = p.Kid?.FirstName
KidLastName = p.Kid?.LastName
})
.FirstOrDefault();
if (data.KidFirstName==null && data.KidLastName==null)
{
return Ok(new {data.Dad});
}
else
{
return Ok(new {data.Dad,Kid=$"{data.KidFirstName} {data.KidLastName}");
}
I have a DTo class UserDTO with the following constructor public UserDTO(User user). I have also created an NHibernate query which retrieves a IList<TodoDTO> where each TodoDTO has the following property public IList<UserDTO> ResponsibleUsers { get; set; }.
I am wondering if it would be possible to this constructor on UserDTO in my query, so something like this:
var responsibleUsers = session.QueryOver<UserTodo>()
.JoinAlias(ut => ut.User, () => userAlias)
.Where(ut => ut.Todo.Id.IsIn(_todos.Select(t => t.Id).ToArray()))
.Select(u => new UserDTO(userAlias)).ToList<UserDTO>();
The constructor looks like this:
public UserDTO(User user) {}
The problem is that when I run this code the parameter in the UserDTO constructor the user is null.
Calling a Constructor, (or any other code) in the syntax of a query won't work. the entities here are only used to resolve the table and columns for the sql query. The ability to call methods of these objects is missleading...
You can use a Projection to select data from one, or multiple db-entities to a new object (in your case: to the UserDTO)
UserTodo userTodo = null
UserDTO result = null;
var responsibleUsers = session.QueryOver<UserTodo>(() => userTodo)
.JoinAlias(ut => ut.User, () => userAlias)
.Where(ut => ut.Todo.Id.IsIn(_todos.Select(t => t.Id).ToArray()))
.SelectList(list => list
.Select(() => userAlias.FirstName).WithAlias(() => result.FirstName)
.Select(() => userAlias.UserId).WithAlias(() => result.IdOfUser)
.Select(() => userTodo.Age).WithAlias(() => result.Age) // if you require any values from UserTodo
)
.TransformUsing(Transformers.AliasToBean<UserDTO >())
.List<UserDTO >();
We are using EF .NET core for our architecture and want to do a basic query. So all we are after is using both LINQ & EF with lazy loading switched off to select the parent, in this case stick item and some of the fields in the child objects. Then return them back into our strongly typed item.
Something like this.
var qry = _context.Set<stock>()
.Include(p => p.stockitem)
.Where(q => q.customer == accountNo)
.Select(r => new stock() {
customer = r.customer,
r.stockitem.select(s => new {id, s.id, s.name }})
.ToList();
So is it possible to do this? basically get hold of say just a couple of columns from our child object. Then have everything returned in the strongly typed object.
First create a model in which the selected data will be stored (This model is just an example):
public class MyNewCustomer
{
public string CustomerAccount { get; set; }
public Dictionary<int, string> Items { get; set; }
}
After that you can create a new object from your select:
var data = _context.Stocks
.Include(p => p.stockitem)
.Where(p => p.customer == accountNo)
.Select(p => new MyNewCustomer
{
CustomerAccount = p.customer,
Items = p.stockitem.ToDictionary(s => s.id, s => s.name)
}).ToList();
PS: I used Dictionary because you didn't provide the actual model. You can choose whatever kind of List<TObject> you want.
The structure of my classes and repository:
public class Group{
//fields
}
public class User{
public UserRole Role {get;set;}
}
public abstract class UserRole{
//fields
}
public class PersonUserRole:UserRole{
public Group Group {get;set;}
}
public class ManagerUserRole:UserRole{
public IList<Group> Groups {get;set;}
}
An example where I encounter the issue:
public class UserRepository:IUserRepository{
private readonly ApplicationDbContext _dbContext;
private readonly DbSet<User> _users;
public UserRepository(ApplicationDbContext dbcontext)
{
_dbContext = dbcontext;
_users = _dbContext.DomainUsers;
}
public User GetBy(int id)
{
User type = _users.Include(u => u.Role)
.SingleOrDefault(c => c.UserId == id);
if (typeof(PersonUserRole) == type.Role.GetType())
{
return
_users.Include(u => u.Role)
.ThenInclude(r => (r as PersonUserRole).Groep)
.SingleOrDefault(c => c.UserId == id);
}
else
{
return _users.Include(u => u.Role)
.ThenInclude(r => (r as ManagerUserRole).Groups)
.SingleOrDefault(c => c.UserId == id);
}
}
}
I get the following error message:
Message "The property expression 'r => (r As PersonUserRole).Group' is not
valid. The expression should represent a property access: 't =>
t.MyProperty'
It seems like I cannot cast my UserRole Type to the actual PersonUserRole Type to include the Group/Groups property. How can I include the properties of the subclasses?
Update: Starting with version 2.1, Include on derived types is now naturally supported by EF Core via either cast or as operator inside lambda Include / ThenInclude overloads or string Include overload.
Original answer (pre EF Core 2.1):
Currently eager loading of derived entity navigation properties is not supported.
As a workaround you can use a combination of Eager loading, Explicit loading and Querying related entities explained in the Loading Related Data section of the EF Core documentation:
var user = _users.Include(u => u.Role).SingleOrDefault(u => u.UserId == id);
var userRoleQuery = db.Entry(user).Reference(u => u.Role).Query();
if (user.Role is ManagerUserRole)
userRoleQuery.OfType<ManagerUserRole>().Include(r => r.Groups).Load();
else if (user.Role is PersonUserRole)
userRoleQuery.OfType<PersonUserRole>().Include(r => r.Group).Load();
return user;
EntityFramework has an open issue which is exacly the same as your problem: https://github.com/aspnet/EntityFramework/issues/3910
Maybe you can try what they suggest, but I can't try if it works:
var o = context.Set<Order>()
.Where(o => o.Id == 1234)
.Single();
context.Set<GroupPosition>()
.Where(x => x.Order == o)
.Include(x => x.GroupType)
.Load();
context.Set<SalesPosition>()
.Where(x => x.Order == o)
.Include(x => x.Group)
.Load();
I'm using projection of query results to a custom type, which isn't a part of entity data model:
public sealed class AlgoVersionCacheItem : NotificationObject
{
public int OrderId { get; set; }
public string OrderTitle { get; set; }
public int? CurrentVersion { get; set; }
public int CachedVersion { get; set; }
public IEnumerable<int> AvailableVersions { get; set; }
}
I want AvailableVersions to be sorted in descending order. Hence, I've tried to add sorting for AvailableVersions in projection:
return someQueryable
.Select(version => new AlgoVersionCacheItem
{
OrderId = version.OrderId,
OrderTitle = version.Order.Title,
CurrentVersion = version.Order.CurrentAlgoVersionId,
CachedVersion = version.Id,
AvailableVersions = version
.Order
.AlgoVersions
.Where(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id)
.OrderByDescending(v => v.Id) // this line will cause exception
.Select(v => v.Id)
})
.Where(item => item.AvailableVersions.Any())
.OrderByDescending(item => item.OrderId)
.ToArray();
With sorting, execution of the query throws an System.Data.EntityCommandCompilationException with System.InvalidCastException as inner exception:
Unable to cast object of type
'System.Data.Entity.Core.Query.InternalTrees.SortOp' to type
'System.Data.Entity.Core.Query.InternalTrees.ProjectOp'
Without .OrderByDescending(v => v.Id) everything works fine.
Is this yet another feature, that isn't supported in Entity Framework, or I've missed something?
P.S. I know, that I can sort items later at client side, but I'm wondering about sorting at the server side.
This is a bug in EF. I was able to repro this on both EF5 and EF6. I think you should be able to workaround the bug by filtering records before creating the results i.e.:
return someQueryable
.Where(version => version.Order.AlgoVersions.Any(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id))
.Select(version => new AlgoVersionCacheItem
{
OrderId = version.OrderId,
OrderTitle = version.Order.Title,
CurrentVersion = version.Order.CurrentAlgoVersionId,
CachedVersion = version.Id,
AvailableVersions = version
.Order
.AlgoVersions
.Where(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id)
.OrderByDescending(v => v.Id) // this line will cause exception
.Select(v => v.Id)
})
.OrderByDescending(item => item.OrderId)
.ToArray();
I also have a feeling that this query could be simplified if you go from the other side of relationship (i.e. from Orders) but it may depend on how the someQueryable is created.
In my case the Linq query was selecting a new anonymous construct with one property being set to a collection ToList() extension method. I removed the embedded execution, the error went away, and the system still worked fine.