I use Entity Framework 6.0.1 and I have next problem:
I have the next db structure:
public class User
{
int Id { get; set;}
string E-mail {get; set;}
string Name {get; set;}
...
}
class House
{
string Id {get; set;}
string Name { get; set; }
string Street { get; set; }
. . .
IQueryable<User> Users { get; set; }
}
Every House may be linked to many Users. Any User may be linked to many Houses.
I need to create a query in which to get a list of houses to which is attached a particular user
I know only user Id.
I wrote the next statement:
var houses = this.context.Houses
.Where(house => house.Users.Any(i => i.Id == my_searched_user_id));
but I get error: LINQ to Entities does not recognize the method 'System.String ElementAtOrDefault[String](System.Collections.Generic.IEnumerable`1[System.String], Int32)' method, and this method cannot be translated into a store expression.
I change it to
var houses = this.context.Houses
.Where(house => house.Users.**ToList()**.Any(i => i.Id == my_searched_user_id));
but without any luck :(
You added ToList in wrong place. Try next code:
var houses = this.context.Houses
.Where(house => house.Users.Any(i => i.Id == my_searched_user_id))
.ToList();
ToList will cast IQueryable to IEnumerable, so the query will be executed.
You should not define your Users collection as an IQueryable. It should instead be an ICollection:
public virtual ICollection<User> Users {get;set}
I'm surprised that even compiles... but still, that's not really your problem. Your problem is in code you have not shown. You need to show all the code because something else is causing this error, something to do with indexing a string variable.
Related
I need your help
I try to create a linq sentence with .Include but my problem is that i have a property in mi class witch is a list, it is my class specifically:
public partial class document
{
public int ID { get; set; }
public string Amount { get; set; }
public List<Log> Log { get; set; }
}
this is the class log
public partial class Log
{
[Key]
public int ID { get; set; }
[Required]
public Status Status { get; set; }
[Column(TypeName = "text")]
public string Description { get; set; }
public DateTime? DateLog { get; set; }
public int? DocumentID{ get; set; }
[ForeignKey("DocumentID")]
public Document Document{ get; set; }
}
my problem is that I don't know how to filter my list record inside the document for include in the class, I need to get the whole document class and filter the log that only shows status = recieved, a document can have many logs
y tried to do that but it didnĀ“t work
var Result = db.document
.Include(m => m.Log.Where(c => c.Status == Status.Recieved));
i recived the next error
"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.\r\nparameter name: path"
I appreciate your help
Include used for include relationships with an entity and fetch related entity properties, check documentation - Fetching related data
If you select documents without Include like this
var documents = await db.document.ToListAsync();
you get documents data where Log will be null.
You need something like that:
var result = await db.document
.Select(w=> new
{
document = w,
log = w.Log.Where(c => c.Status == Status.Recieved).ToList()
}).ToListAsync();
EF does support some automatic filtering rules to help with concepts like soft-delete (IsActive) and multi-tenancy (ClientId), but not really applicable for scenarios like this where you want to apply a situational filter like "received" documents.
EF entities should be considered as models reflecting the data state. To filter results like that is more of a view model state which you can achieve through projection:
var result = db.document.Select(d => new DocumentViewModel
{
DocumentId = d.DocumentId,
// .. fill in other required details...
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.Select(l => new LogViewModel
{
// Fill needed log details...
}).ToList()
}).ToList();
Otherwise if you are doing something local with the entities and just want the document and the received log entries:
var documentDetails = db.document
.Where(d => d.DocumentId == documentId)
.Select(d => new
{
Document = d,
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.ToList()
}).Single();
documentDetails.Document.Logs will not be eager loaded, and would trigger lazy loading if you access it, but the documentDetails does contain the relevant Received logs to access. As an anonymous type it's not suitable to being returned, only consumed locally.
Problem
I want to show a single pet on my details page. I'm having trouble understanding how to write LINQ(Method Syntax) to pull a single pet/item from the database.
My understanding so far
When working with LINQ, it's shining purpose is to be able to loop through an IEnumerable array or list in the database. As you look in my HomeController, I feel fairly confident that I have the right pieces of code to build what I want MINUS the query.
Request
Using method syntax, what query should I be using to pull a single pet?
HomeController.cs
[HttpGet("pet/{PetId}")]
public IActionResult Detail(Pet singlePet)
{
List<Pet> Animal = _context.Pets
//***MISSING QUERY CODE HERE***
return View("Pet", singlePet);
}
Details.cshtml
#model PetViewModel
<div>
<h1>Pet Shelter</h1>
#foreach (Pet creature in Model.Pets)
{
<h3>Details about: #creature.Name</h3>
<div>
<p>Pet Type: #creature.Type</p>
<p>Description: #creature.Description</p>
<p>Skill One:#creature.Skill1</p>
<p>Skill Two:#creature.Skill2</p>
<p>Skill Three:#creature.Skill3</p>
</div>
}
</div>
PetViewModel.cs
using System.Collections.Generic;
namespace petShelter.Models
{
public class PetViewModel
{
public Pet Animal { get; set; }
public List<Pet> Pets { get; set; }
public List<Owner> Owners { get; set; }
public Owner Owner { get; set; }
}
}
Pet.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace petShelter.Models
{
public class Pet
{
[Key]
public int PetId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Type { get; set; }
[Required]
public string Description { get; set; }
public string Skill1 { get; set; }
public string Skill2 { get; set; }
public string Skill3 { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Owner Owner { get; set; }
public int? OwnerId { get; set; }
}
}
Short Version
Use
var singlePet=_context.Pets.Find(someId);
Or
var singlePet=_context.Pets.FirstOrDefault(x=>x.PetId=someId);
Explanation
LINQ queries don't query lists. They're translated by a LINQ provider to whatever the target data storage understands. When querying a database, they're translated to SQL.
List<Pet> Animal = _context.Pets won't compile, which is a very good thing - if it did, it would load the entire table in memory instead of querying it. Filtering will be performed on the client, without the benefit of indexing. Performance will be orders of magnitude worse than a simple SQL query due to wasted IO, wasted RAM and even worse, taking locks on every row in the table when only one was needed. Excessive locks will cause delays for every other request that tries to use the same table.
Pets is a DbSet<T>, not a List<T>. It's not a container, it represents an entity without holding any data itself. It can be used to write queries that translate to SQL.
Assuming PetId is the primary key for Pet, the easiest and fastest way to load a single record is calling DbSet.Find. This method retrieves an object using the supplied primary key values.
This query:
var singlePet=_context.Pets.Find(someId);
translates to
select ...
from Pets
where PetID=#id
This will load and cache a Pet instance inside DbContext. After that, every time Find is called with the same PK value during the same request, the cached object will be returned.
The query
var singlePet=_context.Pets.FirstOrDefault(x=>x.PetId=someId);
Will translate to
select top 1 ...
from Pets
where PetID=#id
Nothing is cached in this case.
If you don't care and just want a single result from the database you can do something like this
var singlePet = _context.Pets.First();
If you want the first result that matches an expression you can do something like this
var singlePet = _context.Pets.First( e => e.Id == '1');
If you are expecting ONLY a single result that matches an expression you can do something like this
var singlePet = _context.Pets.Single( e => e.PetId == '1' );
If you want just one, with no filter at all on which, you can use this, it will get you the first on the list:
var aSpecificPet = Pets.FirstOrDefault();
If you need to specify some kind of condition to determine which pet to bring, you can use something like:
var aMoreSpecificPet = Pets.FirstOrDefault(x=>x.Type == "Dog");
Take in mind that whatever you pass as a condition will filter the Pets list, and with FirstOrDefault you will get the first (if there are any) pets that match your criteria.
Also, default value is usually null (in case no pets match your criteria), so its a good idea to always add a line like
if(pet != null){
your code
}
If you want all pets that match that criteria, use:
var allDogsInPets = Pets.Where(x=>x.Type == "Dog").ToList();
The .ToList() returns a new list with the pets that match your criteria.
I have been looking through other examples on SO and I am still unable to resolve this.
I have the following model structure
public class Event
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class Category
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class EventCategory
{
[Key]
public int ID { get; set; }
public int EventID{ get; set; }
public Event Event{ get; set; }
public int CategoryID{ get; set; }
public Category Category{ get; set; }
}
From my Events controller I am trying to use a LINQ query to only show Events where the CategoryID is equal to 1 but i keep on coming into errors with my WHERE clause I think.
UPDATE:
I have been trying multiple queries but at present it is
var eventsContext = _context.Events
.Include(e => e.EventCategories)
.Include(e=>e.EventCategories.Select(ms => ms.Category))
.Where(e=>e.ID==1)
.Take(15)
.OrderByDescending(o => o.StartDate);
This is the error I get
TIA
First, the lambda passed to Include must be a model expression. Specifically, that means you cannot use something like Select. If you're trying to include EventCategories.Category, then you should actually do:
.Include(e => e.EventCategories).ThenInclude(ms => ms.Category)
That will fix your immediate error. The next issue is that the way in which you're attempting to query the category ID is incorrect. The lamdas don't carry over from one clause to the next. In other words, when you're doing Where(e => e.ID == 1), e is Event, not Category. The fact that you just included Category doesn't limit the where clause to that context. Therefore, what you actually need is:
.Where(e => e.EventCategories.Any(c => c.CategoryID == 1))
For what it's worth, you could also write that as:
.Where(e => e.EventCategories.Any(c => c.Category.ID == 1))
Notice the . between Category and ID. Now this where clause requires joins to be made between all of Event, EventCategories, and Category, which then means you don't actually need your Include(...).ThenInclude(...) statement, since all this does is tell EF to make the same JOINs it's already making. I will still usually do the includes explicitly, though, as otherwise, if your where clause were to change in some future iteration, you may end up no longer implicitly including everything you actually want included. Just food for thought.
I'm using MVC5 EF6 and Identity 2.1.
I have two classes:
public class Incident
{
public int IncidentId {get; set;}
...//Title, Description, etc
public virtual ICollection<FollowedIncident> FollowedIncidents { get; set; }
public virtual ApplicationUser User { get; set; }
}
public class FollowedIncident
{
public int FollowedIncidentId { get; set; }
public string UserId { get; set; }
public int IncidentId { get; set; }
public virtual Incident Incident { get; set; }
public virtual ApplicationUser User { get; set; }
}
So, the users will have the ability to follow an incident. (For starters, I'm not entirely sure if I need the ICollection and public virtual relationship references, but added them just in case for the time being.)
I'm trying to create the query that will show users the results of their followed incidents. In my controller, my query starts like this (I'm using Troy Goode's paging package... i.e. listUnpaged):
IQueryable<Incident> listUnpaged = db.Incidents.OrderByDescending(d => d.IncidentDate);
Then I want to filter by followed incidents. So, I want to show incidents where userId (parameter I pass to it) is equal to UserId in FollowedIncident. I've tried like this (error about conversion to bool from IEnumerable):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.Where(t => t.UserId == userId));
And this (no error, but doesn't filter at all):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.All(t => t.UserId == userId));
To me, it seems it should be as simple as this:
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.UserId == userId));
But, the linq extensions don't seem to like related data child properties? (I apologize for my programming terminology as I haven't quite pieced together all the names for everything yet.)
Anyone know how to accomplish this? It seems I may not even be thinking about it correct? (...since in the past, I've always used related data to supplement or add properties to a result. This will be the first time I want to narrow results by related data.)
Thank you.
Actually you're going about getting the Incidents the wrong way.. since Incident is a navigation property of FollowedIncident you should just use
IQueryable<Incident> listUnpaged = db.FollowedIncidents
.Where(a => a.UserId == userid)
.Select(a => a.Incident)
.OrderByDescending(d => d.IncidentDate);
Another option is to use Any()
IQueryable<Incident> listUnpaged = db.Incidents
.Where(a => a.FollowedIncidents.Any(b => b.UserId == userid)
.OrderByDescending(d => d.IncidentDate);
which would be like saying
Select *
From Incidents
Where Id IN (Select IncidentId
From FollowedIncident
Where UserId = #UserId)
I have a problem in Entity-Framework, using Code-First, that I couldn't solve.
Having entities of the type
public class Product {
public int ID {get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category {
public int ID {get; set;}
public virtual ICollection<Product> Products { get; set; }
// rest omitted
}
in my database, i try to get all Products that have at least one Category from a list of given Categories. I need an Expression as this expression is combined with other expressions later.
Ie. i tried:
var searchFor = new List<Category>{...};
var expression = product => product.Categories.Any(cat => searchFor.Contains(cat))
Executing this later against a DbContext
context.Products.Where(expression).ToList();
creates an exception stating mainly that This context supports primitive types only.
Changing it to
var expression = product => product.Categories.Any(
cat => searchFor.Any(d => d.ID == cat.ID));
to get rid of the object comparison didn't help. I'm stuck. How can I manage that?
You should get rid of List<Category>, replacing it with a list of IDs, like this:
// I'm assuming that ID is of type long; please fix as necessary
var searchFor = new List<long>{...};
var expression = product =>
product.Categories.Any(cat => searchFor.Contains(cat.ID))
If you've already got a list of categories, you can build a list of IDs outside the query:
var searchForIds = searchFor.Select(x => x.ID).ToList();
var query = context.Products
.Where(product => product.Categories
.Any(cat => searchForIds.Contains(cat.ID)));
I don't know that that will work, but it might. (Apologies for the indentation... it's just to avoid scrolling.)