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

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;
}

Related

Entity Framework Core 3.1 is throwing "Include has been used on non entity queryable" exception

I have an ASP.NET Core 3.1 based project where I am using Entity Framework Core 3.1 as an ORM.
I have the following two entity models
public class PropertyToList
{
public int Id { get; set; }
public int ListId { get; set; }
public int UserId { get; set; }
public int PropertyId { get; set; }
// ... Other properties removed for the sake of simplicity
public virtual Property Property { get; set; }
}
public class Property
{
public int Id { get; set; }
// ... Other properties removed for the sake of simplicity
public int TypeId { get; set; }
public int StatusId { get; set; }
public int CityId { get; set; }
public virtual Type Type { get; set; }
public virtual Status Status { get; set; }
public virtual City City { get; set; }
}
I am trying to query all properties where a user has relation to. The PropertyToList object tells me if a user is related to a property. Here is what I have done
// I start the query at a relation object
IQueryable<Property> query = DataContext.PropertyToLists.Where(x => x.Selected == true)
.Where(x => x.UserId == userId && x.ListId == listId)
// After identifying the relations that I need,
// I only need to property object "which is a virtual property in" the relation object
.Select(x => x.Property)
// Here I am including relations from the Property virtual property which are virtual properties
// on the Property
.Include(x => x.City)
.Include(x => x.Type)
.Include(x => x.Status);
List<Property> properties = await query.ToListAsync();
But that code is throwing this error
Include has been used on non entity queryable
What could be causing this problem? How can I fix it?
Put your includes right after referencing the parent entity.
You can do ThenInclude to bring the child entities of the included entities also. You'll need to do one Include for each ThenInclude.
Then you can do your selecting after including / filtering. Something like:
var query = DataContext.PropertyToLists
.Include(p => p.Property).ThenInclude(p => p.City)
.Include(p => p.Property).ThenInclude(p => p.Type)
.Include(p => p.Property).ThenInclude(p => p.Status)
.Where(p => p.Selected == true && p.UserId == userId && p.ListId == listId)
.Select(p => p.Property);
An observation, your domain models PropertyToList and Property both have virtual properties. On top of that you are using Include operator to select these properties.
This is not necessary, when property is defined with virtual, then it will be lazy loaded. So an Include is not needed. Lazy loading is not the recommended way, using include is better so you select only graph properties that are required.

System.InvalidOperationException when using GroupBy

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

Entity Framework Core 3.0 query not pulling the required information

I am trying to fetch data from two tables that has one to many relationship in Entity Framework Core 3. The models that represent the tables are shown below. One Person can have many PersonNotes. I need to query the PersonNotes table by passing the PersonId. I am currently getting an error saying Person does not contain the definition of Contains. How do I formulate this query.
class Person
{
[Key]
public int Id { get; set; }
public List<PersonNote> PersonNotes { get; set; }
}
class PersonNote
{
[Key]
public int Id { get; set; }
public int PersonId { get; set; }
}
class StackOverflow : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<PersonNote> PersonNotes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.PersonNotes)
.WithOne()
.HasForeignKey(p => p.PersonId);
}
}
Query
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var PersonNotes1 = PersonNotes.Where(p => Person.Contains(p.Id));
return PersonNotes1;
}
To get PersonNotes by one PersonID:
IQueryable<PersonNote> GetPersonNotesSingleId(int ID) =>
context.PersonNotes.Where(p => p.PersonId == ID);
To get PersonNotes by multiple PersonIDs:
IQueryable<PersonNote> GetPersonNotesMultipleId(IEnumerable<int> IDs) =>
context.PersonNotes.Where(p => IDs.Contains(p.PersonId));
The last query will generate IN T-SQL clause:
var notes = GetPersonNotesMultipleId(new[] { 1, 2, 3 });

How do I include the dependent entities of an inherited entity with entity framework core that uses Discriminator column?

Given following models with inheritance:
public class Credential { ... }
public class LocationCredential: Credential {
...
public long LocationId {get; set;}
public Location Location {get; set;}
}
public class EducationCredential: Credential {
...
public long SchoolId {get; set;}
public Location School {get; set;}
}
public class School { ... }
public class Location { ... }
Entity framework core creates only one Credentials table with a Discriminator column to identify the inherited class in the db.
How can I include the dependent entities when querying the credentials?
I would like to do something like this:
var cred = await context.Credentials
.Where( c => c.CredentialId == 123)
.Include(c => c.Location) // cannot do these beacause they are not
.Include(c => c.School) // properties of Credential class
.FirstOrDefaultAsync();
I don't want to do separate queries for each inherited class and then return the non-null one. Don't want these:
var cred = await context.EducationCredentials
.Where( c => c.CredentialId == 123)
.Include(c => c.School)
.FirstOrDefaultAsync();
if (cred == null) {
// try same thing with LocationCredentials
}
...
I ended up doing this; I still don't like it because it requires two calls to the db;
var cred = await _context.Credentials
.Where(c => c.CredentialId == credentialId)
.FirstOrDefaultAsync();
if (cred.GetType() == typeof(LocationCredential)) {
return await _context.LocationCredentials
.Where(e => e.CredentialId == credentialId)
.Include(e => e.Location)
.FirstOrDefaultAsync();
}
...
[NotMapped]
public class LocationCredential: Credential
{
...
public long LocationId {get; set;}
public Location Location {get; set;}
}
use [NotMapped] Attributes on your model like this.
this is old thread, but i found nothing how to solve this problem and decided to write possible solution here . may be it helps somebody sometime
for the short answer fastforward to cast in ThenInclude
model
public enum InventoryType {ANY, MONEY, STOCK}
[Table ("Inventory")]
public class cInventory
{
...
public InventoryType InventoryType {get;set;}
public List<cInventoryVersion>Versions {get;set}
....
}
[Table("InventoryVersions")]
public abstract class cInventoryVersion
{
......
//descriminator
public InventoryType InvType {get;set;}
}
public class cIMVersion : cInventoryVersion
{
public List<cIMItems> Items {get;set;}
}
public class cISVersion : cInventoryVersion
{
public List<cISItems> Items {get;set;}
}
[Table("IMItems")]
public class cIMItems
{
...
}
Table("ISItems")]
public class cISItems
{
...
}
context configuration
public virtual DbSet<cInventory> tblInventories { get; set; }
public virtual DbSet<cInventoryVersion> tblInventoryVersions { get; set; }
public virtual DbSet<cISVersion> tblISVersions { get; set; }
public virtual DbSet<cIMVersion> tblIMVersions { get; set; }
onModelCreating
modelBuilder.Entity<cInventoryVersion>(e =>
{
e.HasDiscriminator<InventoryType>(e => e.InvType)
.HasValue<cISVersion>(InventoryType.STOCK)
.HasValue<cIMVersion>(InventoryType.MONEY);
});
modelBuilder.Entity<cISVersion>(e =>
{
e.HasMany(e => e.Items).WithOne().OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<cIMVersion>(e =>
{
e.HasMany(e => e.Items).WithOne().OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<cInventory>(e =>
{
e.HasMany(e => e.Versions).WithOne().OnDelete(deleteBehavior: DeleteBehavior.Cascade);
});
Include correct items with descriminator
public void LoadData(int invid)
{
_inventory = _ourDB.tblInventories
.Include(i => i.Org)
.Include(i => i.CreatedBy)
.Include(i => i.ClosedBy)
.Include(i => i.Versions).ThenInclude(i => (i as cIMVersion).Items)
.Include(i => i.FinishedBy).FirstOrDefault();
}

Query does not return child collections

I still struggle with this, why each of 'Category' items returns null 'Task' collections. I do have data in the database, what am I missing?
public class ApplicationUser : IdentityUser
{
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public DateTime Timestamp { get; set; }
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public string Name { get; set; }
public DateTime Timestamp { get; set; }
}
And here is the query:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Users.Where(x => x.UserName == name)
.SelectMany(x => x.Categories)
.Include(x => x.Tasks).ToList();
}
Your query is falling into Ignored Includes case:
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
As explained in the link, if you add the following to your DbContext OnConfiguring:
optionsBuilder.ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning));
then instead null collection you'll get InvalidOperationException containing something like this inside the error message:
The Include operation for navigation: 'x.Tasks' was ignored because the target navigation is not reachable in the final query results.
So how to fix that? Apparently the requirement is to start the query from the entity for which you want to add includes. In your case, you should start from _ctx.Categories. But in order to apply the same filter, you need to add the reverse navigation property of the Application.Users to the Category class:
public class Category
{
// ...
public ApplicationUser ApplicationUser { get; set; }
}
Now the following will work:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Categories
.Where(c => c.ApplicationUser.UserName == name)
.Include(c => c.Tasks)
.ToList();
}
Try this:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Users
.Include(u => u.Categories)
.Include(u => u.Categories.Select(c => c.Tasks))
.Where(x => x.UserName == name)
.SelectMany(x => x.Categories)
.ToList();
}
public virtual ICollection<Task> Tasks { get; set; }