System.InvalidOperationException when using GroupBy - entity-framework-core

I have the following EntityFramework Core 3.1 query:
var counts = await postTags
.GroupBy(x => x.Tag)
.Select(x => new Model {
Tag = new TagModel {
Id = x.Key.Id,
Name = x.Key.Name
},
PostCount = x.Count()
})
.ToListAsync();
Where the entities are:
public class Tag {
public Int32 TagId { get; set; }
public String Name { get; set; } 
public virtual Collection<PostTag> PostTags { get; set; } 
}
public class PostTag {
public Int32 PostId { get; set; }
public Int32 TagId { get; set; }
public virtual Post Post { get; set; } 
public virtual Tag Tag { get; set; } 
}
public class Post {
public Int32 PostId { get; set; }
public String Name { get; set; } 
public virtual Collection<PostTag> PostTags { get; set; } 
}
The objective is count how many Posts each tag is associated.
When I run the query I get the following error:
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll: 'The LINQ expression 'DbSet<PostTag>
.Join(
outer: DbSet<Tag>,
inner: j => EF.Property<Nullable<int>>(j, "TagId"),
outerKeySelector: s => EF.Property<Nullable<int>>(s, "Id"),
innerKeySelector: (o, i) => new TransparentIdentifier<PostTag, Tag>(
Outer = o,
Inner = i
))
.GroupBy(
source: j => j.Inner,
keySelector: j => j.Outer)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
Am I missing something?

The problem in the group is that Tag is a navigation property, so can't use it as a column. In order to fix that use TagId and Name from Tag nav. prop instead, which are the two columns I think you want to group:
var counts = await postTags
.GroupBy(x => new{ x.Tag.TagId, x.Tag.Name)
.Select(x => new Model {
Tag = new TagModel {
Id = x.Key.TagId,
Name = x.Key.Name
},
PostCount = x.Count()
})
.ToListAsync();

Related

EF Core 3.0 Select Projection with index overload (aka .Select((entity, index) => new {}) fails

I have current setup, with a Select indexer-projection (entity, index) (see SubRubrics). If i leave the indexer out, the problem is solved... However if I leave out the SubRubricItems then I can use the indexer. Is it only on the last select projection I can use it, or..?
Below linq projection, error message and more info.
await _db
.Exams
.AsNoTracking()
.Include(exam => exam.Stations)
.ThenInclude(station => station.Rubrics)
.ThenInclude(rubric => rubric.SubRubrics)
.ThenInclude(subRubric => subRubric.Items)
.Select(exam => new Result.ExamViewModel
{
Id = exam.Id,
Name = exam.Name,
Stations = exam.Stations.Select(station => new Result.StationViewModel
{
Id = station.Id,
Description = station.Description,
Rubrics = station.Rubrics.Select(rubric => new Result.RubricViewModel
{
Id = rubric.Id,
Name = rubric.Name,
Info = rubric.Info,
SubRubrics = rubric.SubRubrics.Select((subRubric, index) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
})
})
})
})
.ToListAsync()
This provides this error which I don't understand :/
InvalidOperationException: Processing of the LINQ expression '(MaterializeCollectionNavigation(
navigation: Navigation: Rubric.SubRubrics,
subquery: (NavigationExpansionExpression
Source: DbSet<SubRubric>
.Where(s0 => !(s0.IsDeleted))
.Where(s0 => EF.Property<Nullable<long>>(r, "Id") != null && EF.Property<Nullable<long>>(r, "Id") == EF.Property<Nullable<long>>(s0, "RubricId"))
PendingSelector: s0 => (NavigationTreeExpression
Value: (EntityReference: SubRubric | IncludePaths: Items)
Expression: s0)
)
.Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") == EF.Property<Nullable<long>>(i, "RubricId")))
.AsQueryable()
.Select((subRubric, index) => new SubRubricViewModel{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items
.AsQueryable()
.Select(item => new SubRubricItemViewModel{
Id = item.Id,
Name = item.Name
}
)
}
)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
This used to work, until I added the extra SubRubricItems select for the Items model, aka
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
For reference sake, this is the viewmodel that's being projected into:
public sealed class Result
{
public IEnumerable<ExamViewModel> Exams { get; set; }
public sealed class ExamViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<StationViewModel> Stations { get; set; }
}
public sealed class StationViewModel
{
public long Id { get; set; }
public string Description { get; set; }
public IEnumerable<RubricViewModel> Rubrics { get; set; }
}
public sealed class RubricViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public IEnumerable<SubRubricViewModel> SubRubrics { get; set; }
}
public sealed class SubRubricViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
public IEnumerable<SubRubricItemViewModel> Items { get; set; }
}
public sealed class SubRubricItemViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
}
}
That can't be translated to SQL. So either run the SQL query before the .Select(),
.ThenInclude(subRubric => subRubric.Items)
.AsEnumerable()
.Select(exam => new Result.ExamViewModel
or remove the Includes (they don't do anything when you have a custom projection, and thereby change the query)
SubRubrics = rubric.SubRubrics.Select((subRubric) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = 0, . . .
and fill in the Order property on the view models afterwards.

EF Core - LINQ - Assign property to IQueryable<T>

I have some Entities I want to query by using LINQ:
public class Link: Entity, IAggregateRoot
{
public Guid RelationId { get; private set; }
public Guid ConstructionId { get; private set; }
public virtual Relation Relation { get; private set; }
public virtual Construction Construction { get; private set; }
private Link()
{
}
}
public class Relation: Entity, IAggregateRoot
{
public string Number { get; private set; }
public Guid PersonId { get; private set; }
public Guid RelationTypeId { get; private set; }
public virtual Person Person { get; private set; }
public virtual RelationType RelationType { get; private set; }
[NotMapped]
public int ContextKey { get; set; }
private Relation()
{
}
}
I have a query that returns the relations by giving the construction id and using the Link entity. The query is as follows and it works as expected:
public IQueryable<Relation> GetRelationsByConstructionId(Guid constructionId)
{
var links base.Get(x => x.Construction.ConstructionId == constructionId);
var relations = links.Include(x => x.Relation)
.Select(x => x.Relation)
.Include(x => x.Person)
.Include(x => x.RelationType);
return relations;
}
In Relation I have a NotMapped element ContextKey and I want to set this ContextKey in the query call (e.g. I want to set it to 30). Basically I want to do something like this to extend the query I use:
public IQueryable<Relation> GetRelationsByConstructionId(Guid constructionId)
{
var links base.Get(x => x.Construction.ConstructionId == constructionId);
var relations = links.Include(x => x.Relation)
.Select(x => x.Relation)
.Include(x => x.Person)
.Include(x => x.RelationType);
var updatedRelations = relations.ForEachAsync(x => x.ContextKey = 30);
return updatedRelations;
}
Ofcourse this does not work, because after the ForEachAsync the type of updatedRelations is Task and the return type that is expected has to be IQueryable < Relation >.
How can I make my query work and get the output I want in the correct IQueryable < Relation > type?
This is one of the reasons I'm against unmapped properties - they doesn't fit in LINQ to Entities queries.
You can use the following LINQ to Objects trick:
var updatedRelations = relations
.AsEnumerable()
.Select(x => { x.ContextKey = 30; return x; })
.AsQueryable();
but note that this isn't a real EF Core queryable, so further operations like filtering, ordering, paging etc. on it will be performed in memory on the materialized result of the query before AsEnumerable().
I fixed it myself by updating the query as follows:
public IQueryable<Relation> GetRelationsByConstructionId(Guid constructionId)
{
var links base.Get(x => x.Construction.ConstructionId == constructionId);
var relations = links.Include(x => x.Relation)
.Select(x => x.Relation)
.Include(x => x.Person)
.Include(x => x.RelationType);
relations.ToList().ForEach(x => x.ContextKey = 30);
return relations;
}

Joining data with Entity framework

I'm trying to retrieve all employments linked to employees with a given UserID + all employments linked to managers with a given UserID. I'm joining the sets of data the way SQL UNION would do. This is the code I wrote for this purpose, but it seems that INCLUDEs are cumulative (the second works on the set of data retrieved by the first), which is not my intention (the second set of data has to add to the first):
var list = await db.Employment.AsNoTracking()
.Where(x => x.Active)
.Include(x => x.Employee).Where(x => x.Employee.UserID == UserID)
.Include(x => x.Manager).Where(x => x.Manager.UserID == UserID)
.ToListAsync();
Employment model
public class Employment : IHasID, IValidatableObject
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public Employee Employee { get; set; }
public bool Active { get; set; }
public int? ManagerID { get; set; }
public Employee Manager { get; set; }
}
Employee model
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? UserID { get; set; }
public User User { get; set; }
public bool Active { get; set; }
[InverseProperty("Employee")]
public List<Employment> EmploymentEmployee { get; set; }
[InverseProperty("Manager")]
public List<Employment> EmploymentManager { get; set; }
}
Later, I discovered that there's a Union extension, so I tried to use it:
var list = await
db.Employment.Where(x => x.Active).Include(x => x.Employee)
.Include(x => x.Manager).Where(x => x.Employee.UserID == UserID)
.Union(
db.Employment.Where(x => x.Active).Include(x => x.Employee)
.Include(x => x.Manager).Where(x => x.Manager.UserID == UserID))
.ToListAsync();
...but this results in an exception: InvalidOperationException: Warning as error exception for warning 'CoreEventId.IncludeIgnoredWarning': The Include operation for navigation: 'x.Employee' was ignored because the target navigation is not reachable in the final query results.

Entity Framework validation failed when updating a class with virtual properties

I'm having problems updating a property of a class when the class contains virtual properties. Here is my code
public class Policy
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
[UIHint("Company"), Required]
public virtual Company company { get; set; }
[UIHint("Productor"), Required]
public virtual Productor productor { get; set; }
[MaxLength(1000)]
public string comments { get; set; }
}
public class Company
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
[MaxLength(100)]
public string name { get; set; }
}
//Then Productor class is the same as company but with another name
public static int updateComment(long id, string comments)
{
MemberPolicies mp = new MemberPolicies();
Policy p = mp.Policies.Single(o => o.id == id);
p.comments = comments;
int afectedRecords = -1;
try
{
afectedRecords = mp.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
return afectedRecords;
}
The properties which are causing the validation errors are company and company, but I only want to update the property comment.
Some help is appreciate.
Thanks
EF does not lazy load your virtual properties when you try to save your entity (damn it).
You can do either of the following.
Use Include:
Policy p = mp.Policies
.Include(p => p.company)
.Include(p => p.productor).Single(o => o.id == id);
p.comments = comments;
Or use Load:
Policy p = mp.Policies.Single(o => o.id == id);
p.comments = comments;
mp.Entry(p).Reference(p => p.company).Load();
mp.Entry(p).Reference(p => p.productor).Load();
Or better you can write more elegant code as explained here.

How to select one to many relationship & one to one together using dapper

I have following classes and db schema. I am trying to query this data from database using dapper that would hydrate the full object graph. I looked at various SO question and the test but couldn't really figure out how to do this.
DB Schema
Author
-AuthorId
-Name
Post
-PostId
-Content
-AuthorId
Comment
-PostId
-CommentId
-Content
Tag
-PostId
-TagId
-Name
Classes
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
}
public class Tag
{
public int PostId { get; set; }
public int TagId { get; set; }
public string Name { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Content { get; set; }
public int AuthorId { get; set; }
public List<Tag> Tags { get; set; }
public List<Comment> Comments { get; set; }
public Author Author { get; set; }
public Post()
{
this.Comments = new List<Comment>();
this.Tags = new List<Tag>();
}
}
public class Comment
{
public int PostId { get; set; }
public int CommentId { get; set; }
public string Content { get; set; }
}
So I ended up doing something like the following.
public static IEnumerable<Post> Map(this SqlMapper.GridReader reader, Func<Post, int> postKey, Func<Comment, int> commentKey, Func<Tag, int> TagKey, Action<Post, IEnumerable<Comment>> addPostComment, Action<Post, IEnumerable<Tag>> addPostTag)
{
var posts = reader.Read<Post>().ToList();
var comments = reader.Read<Comment>().GroupBy(t => commentKey(t)).ToDictionary(g => g.Key, g => g.AsEnumerable());
var tags = reader.Read<Tag>().GroupBy(t => TagKey(t)).ToDictionary(g => g.Key, g => g.AsEnumerable());
var authors = reader.Read<Author>().ToList();
foreach (var post in posts)
{
IEnumerable<Tag> posttags;
if (tags.TryGetValue(postKey(post), out posttags))
{
addPostTag(post, posttags);
}
IEnumerable<Comment> postcomments;
if (comments.TryGetValue(postKey(post), out postcomments))
{
addPostComment(post, postcomments);
}
post.Author = authors.Where(a => a.AuthorId == post.AuthorId).SingleOrDefault();
}
return posts;
}
Using the above extension as below.
var query = "SELECT p.* FROM Post p " +
"SELECT c.* FROM Comment c WHERE c.PostId in ( SELECT PostId FROM Post) " +
"SELECT t.* FROM Tag t WHERE t.PostId in ( SELECT PostId FROM Post) " +
"SELECT a.* FROM Author a WHERE a.AuthorId in ( SELECT AuthorId FROM Post) ";
List<Post> result = new List<Post>();
using (var conn = new System.Data.SqlClient.SqlConnection(ConnectionString))
{
conn.Open();
try
{
var posts = conn.QueryMultiple(query)
.Map
(
p => p.PostId,
c => c.PostId,
t => t.PostId,
(p, c) => p.Comments.AddRange(c),
(p, t) => p.Tags.AddRange(t)
);
if (posts != null && posts.Any())
{
result.AddRange(posts);
}
}
catch (Exception)
{
//TODO: Log Exception
throw;
}
conn.Close();
}