I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value
Related
I need to write some dynamic select expression on entity framework something like in the example.
var list = db.Article
.GroupBy(x => x.CategoryId)
.Select(x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
})
.ToList();
I can write group by with expression like this:
Expression<Func<Article, int>> groupByExp;
groupByExp = (x) => x.CategoryId;
So I can replace actual expression with groupByExp.
var list = db.Article
.GroupBy(groupByExp)
.Select(x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
})
.ToList();
I also want to write another expression for select. So I can send it to another function and it will be dynamic on that function.
Expression<Func<Article, bool>> selectExp;
selectExp = (x) => new ArtDto { ... };
Is it possible? Do you have any idea or tutorial for that?
Yes it is possible,
before start you need to:
Create the new object for selected properties
Map your model to the new object
lets consider that you have your model Article and you need to return the new model ArticleSummary as below
public class Article {
public int id { get; set; }
public string Title { get; set; }
public string Introduction { get; set; }
public string AuthorId { get; set; }
public AppUser Author { get; set; }
public DateTime PublishDate { get; set; }
}
public class ArticleSummary {
public int Id { get; set; }
public string Title { get; set; }
public string Introduction { get; set; }
}
and here is the mapping :
Expression<Func<Article, ArticleSummary>> mapArticle = x => new ArticleSummary {
Id = x.Id,
Title = x.Title,
Introduction = x.Introduction
};
and here is the "simplified" data function :
// T is Article model
// U is ArticleSummary model
public async Task<ICollection<U>> SelectListAsync<T, U>(
Expression<Func<T, bool>> search,
Expression<Func<T, U>> select) where T : class
{
var query =
_context.Set<T>()
.Where(search)
.Select(select);
return await query.ToListAsync();
}
you can call it by passing mapping expression to select property.
Your expression should take IIGrouping<T, Article> as first argument (where T is a type of CategoryId). Assuming that CategoryId is int expression can be written like
public static Expression<Func<IGrouping<int, Article>, ArtDto>> SelectExpression()
{
return x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
};
}
I have the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();
I have a use-case with a deeply nested class hierarchy, for example like this:
public class Parent
{
public int Id { get; set; }
public List<ChildOne> Children { get; set; }
}
public class ChildOne
{
public int Id { get; set; }
public int ParentId { get; set; }
public List<ChildTwo> ChildrenTwo { get; set; }
}
public class ChildTwo
{
public int Id { get; set; }
public int Priority { get; set; }
public int ChildOneId { get; set; }
public List<ChildThree> ChildrenThree { get; set; }
}
public class ChildThree
{
public int Id { get; set; }
public int ChildTwoId { get; set; }
}
If I want to load all parent-objects and their related children levels, I'd do this:
var objects = context.Parent
.Include(parent => parent.Children)
.ThenInclude(childOne => childOne.ChildrenTwo)
.ThenInclude(childTwo => childTwo.ChildrenThree)
.ToList();
But what if I want my ChildrenTwo entities in the eager-loaded navigational property of ChildOne to be ordered by their Priority? I've done some research, and from the links below (and some others), it is apparently not directly possible in EF Core (yet):
https://github.com/aspnet/EntityFrameworkCore/issues/9445
https://github.com/aspnet/EntityFrameworkCore/issues/2919
https://github.com/aspnet/EntityFrameworkCore/issues/9067
So, how can you achieve the ordering of the ChildrenTwo above (by Priority) in a good/clean way that is fast? That probably means most of the work should happen on the DB server and not on the .NET client side. What's the best approach here?
Though it is very late to answer, but it may help the future readers:
I will explain the code:
var authorArticles = await _context.AuthorArticles
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pq => pq.Qualifications)
.ThenInclude(q => q.QualificationSubject)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.ThenInclude(prof => prof.Profession)
.Where(aa => aa.ArticleId == articleId)
.Select(s => new AuthorArticle
{
Author = new Author
{
Affiliation = s.Author.Affiliation,
AvailableAsReviewer = s.Author.AvailableAsReviewer,
Person = new Person
{
Email = s.Author.Person.Email,
FirstName = s.Author.Person.FirstName,
LastName = s.Author.Person.LastName,
MiddleName = s.Author.Person.MiddleName,
Title = s.Author.Person.Title,
FullName = s.Author.Person.FullName,
UserId = s.Author.Person.UserId,
Professions = new Collection<PersonProfession>
{
new PersonProfession
{
// using sorting here!!
Organization = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Organization,
Profession = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Profession
}
},
Qualifications = new Collection<PersonQualification>
{
new PersonQualification
{
QualificationSubject = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationSubject,
QualificationLevelId = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationLevelId
}
}
}
},
IsCorresponding = s.IsCorresponding,
AuthorPosition = s.AuthorPosition
}).ToListAsync();
return authorArticles;
If you simply eager loaded the entities, then at the time of projection; which means when you are selecting the items from the query, you can recreate the object that has already been provided in slightly different way. In my case, I wanted only one profession of the person out of many and same goes for the qualification of the person.
Took help of select from Another SO great answer!
I'm a bit lost here and I've tried a few different ways to tackle it. So far I'm having a hard time writing out the LINQ to do what I want.
I want to take the user input string which can be multiple keywords split either by whitespace or ",".
This here works grabs the whole search term and compares it to the title in the Post or any tag I may have. I want the user to type in "HTML Preview" which would match a post called, "Preview the World" with the tags "HTML", "CSS", etc....
This query won't work...but I'm trying to modify it so that it does work.
public IPagedList<Post> SearchResultList(string searchTerm, int resultsPerPage, int page)
{
string[] terms = searchTerm.Split(null);
TNDbContext context = DataContext;
return context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.Where(c => (c.Title.Contains(searchTerm) || c.Tags.Any(d => d.Name.StartsWith(searchTerm))) || searchTerm == null)
.OrderByDescending(x => x.Views)
.ToPagedList(page, resultsPerPage);
}
I tried writing this instead of the other "Where" statement
.Where(x => (terms.All(y => x.Title.Contains(y))) || terms == null)
but it keeps throwing this error
Cannot compare elements of type 'System.String[]'. Only primitive types, enumeration types and entity types are supported.
FOR REFERENCE:
public class Post
{
public Post()
{
Tags = new HashSet<Tag>();
Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public string Title { get; set; }
public string UrlTitle { get; set; }
public DateTime Date { get; set; }
public DateTime DateEdited { get; set; }
public string Body { get; set; }
public string Preview { get; set; }
public string PhotoPath { get; set; }
public int Views { get; set; }
//Navigational
public ICollection<Tag> Tags { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Tag
{
public Tag()
{
Post = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public int TimesTagWasUsed { get; set; }
//Navigational
public ICollection<Post> Post { get; set; }
}
You need to start with a base query, and then keep adding where clauses to it for each search term. Try this:
TNDbContext context = DataContext;
//Create the base query:
var query = context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.OrderByDescending(x => x.Views);
//Refine this query by adding "where" filters for each search term:
if(!string.IsNullOrWhitespace(searchTerm))
{
string[] terms = searchTerm.Split(" ,".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
foreach(var x in terms)
{
string term = x;
query = query.Where(post => (post.Title.Contains(term) ||
post.Tags.Any(tag => tag.Name.StartsWith(term))));
}
}
//Run the final query to get some results:
var result = query.ToPagedList(page, resultsPerPage);
return result;
You can nest queries with additional 'from' statements, so something like this should work:
var list = (from post in context.Posts.Include(a => a.Tags).Include(b => b.Comments)
from term in terms
where post.Title.Contains(term) || post.Tags.Any(d => d.Name.StartsWith(term))
select post).OrderByDescending(x => x.Views);
I have linked list kind of situation. My DTO looks like this -
public class DTOItem
{
public string ID
{
get;
set;
}
public int? UniqueId
{
get;
set;
}
public string Payload
{
get;
set;
}
//How do I map this guy? It is list of same type.
public List<DTOItem> RelatedItems
{
get;
set;
}
}
How do I map this guy using AutoMapper? I am able to map other members of the class. Data is mapped from another class' collection object that has a different set of member not identical to this class.
public List<DTOItem> RelatedItems
{
get;
set;
}
Thanks in advance.
UPDATE: Here is the code -
Raphael, here is the code:
The Source Objects:
public class ResultsSet
{
public int? ResultId
{
get;
set;
}
public int UID
{
get;
set;
}
//Returns large XML string
public string ResultBlob
{
get;
set;
}
public RelatedItems[] RelatedSet
{
get;
set;
}
}
public class RelatedItems
{
public int Item_ID
{
get;
set;
}
public int Relationship_ID
{
get;
set;
}
public string Description
{
get;
set;
}
}
To map here is the code:
Mapper.CreateMap<ResultSet, DTOItem>()
.ForMember(dest => dest.ID, opt => opt.MapFrom(src => src.ResultID.GetValueOrDefault(0)))
.ForMember(dest => dest.UniqueId, opt => opt.MapFrom(src => src.UID))
.ForMember(dest => dest.Payload, opt => opt.MapFrom(src => src.ResultBlob));
/*
How do I map RelatedSet to RelatedItems here?
*/
Mapper.Map(result, returnResult);
Thanks again.
No need to use AutoMapper for this.
For non-cyclic, relatively flat data, this should do:
static Func<RelatedItems, DTOItem> MapRelated(IEnumerable<ResultsSet> all) {
var map = MapResultSet(all);
return relatedItem => map(all.First(x => x.UID == relatedItem.Item_ID));
}
static Func<ResultsSet, DTOItem> MapResultSet(IEnumerable<ResultsSet> all) {
return s =>
new DTOItem {
ID = s.ResultId.GetOrElse(0).ToString(),
UniqueId = s.UID,
Payload = s.ResultBlob,
RelatedItems = (s.RelatedSet ?? new RelatedItems[0]).Select(MapRelated(all)).ToList()
};
}
Sample usage:
var data = new[] {
new ResultsSet {
UID = 1,
RelatedSet = new[] {
new RelatedItems { Item_ID = 2 },
new RelatedItems { Item_ID = 3 },
},
},
new ResultsSet {
UID = 2,
},
new ResultsSet {
UID = 3,
},
};
var items = data.Select(MapResultSet(data)).ToList();
Debug.Assert(items.Count == 3);
Debug.Assert(items[0].UniqueId == 1);
Debug.Assert(items[1].UniqueId == 2);
Debug.Assert(items[2].UniqueId == 3);
Debug.Assert(items[0].RelatedItems.Count == 2);
Debug.Assert(items[0].RelatedItems[0].UniqueId == items[1].UniqueId);
Debug.Assert(items[0].RelatedItems[1].UniqueId == items[2].UniqueId);
I assumed Item_ID is the 'key' to UID, otherwise simply adjust MapRelated.
Generally speaking, I think AutoMapper is only useful if you have to map untyped data into typed data, and even in that case I'd think really hard before using it. Otherwise, some LINQ code is simpler and more type safe.