Using Contains() on Included entity - c#

I have the following code:
public PaginatedList<PdModel> PdModel { get; set; }
public async Task OnGetAsync(int id, int? pageIndex, string searchString)
{
IQueryable<PdModel> PdModelsQuer = _context.PdModel.Where(x => x.Id == id)
.Include(x => x.PdTables)
.Include(x => x.pdFolderTree)
.Include(x => x.PdReferences.Where(y=>y.ReferenceName.Contains(searchString)))
.Include(x => x.pdViews)
.Include(x => x.pdDomains)
.Include(x => x.PdModelSources)
.Include(x => x.pdModelExtendeds)
.Include(x => x.pdRules);
PdModel = await PaginatedList<PdModel>.CreateAsync(PdModelsQuer, 1, 10);
}
On code execution I am getting this error:
InvalidOperationException: The property expression 'x => {from PdReference y in x.PdReferences where [y].ReferenceName.Contains(__searchString_1) select [y]}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
I guess I have to use Contains() on included property in another way. I tried a lot of things, but no reasonable code seems to be working.
Anybody can help me on this?
Thanks a lot in advance
My Domain models:
public class PdModel
{
[Key]
public int Id { get; set; }
public string ModelCode { get; set; }
public string ModelName { get; set; }
public string ModelComment { get; set; }
public string ModelDescription { get; set; }
public string ModelAnnotation { get; set; }
public string ModelDatabase { get; set; }
public DateTime? ModelCreationDate { get; set; }
public string ModelCreationUser { get; set; }
public DateTime? ModelModificationDate { get; set; }
public string ModelModificationUser { get; set; }
public string ModelGarantExtendedFlag { get; set; }
public string ModelColumnExtendedFlag { get; set; }
public string ModelTableExtendedFlag { get; set; }
public DateTime PdInsertedDate { get; set; }
public ICollection<PdRule> pdRules { get; set; }
public ICollection<PdModelExtended> pdModelExtendeds {get;set;}
public ICollection<PdTable> PdTables { get; set; }
public ICollection<PdReference> PdReferences { get; set; }
public ICollection<PdModelSource> PdModelSources { get; set; }
public ICollection<PdDomain> pdDomains { get; set; }
public ICollection<PdView> pdViews { get; set; }
[ForeignKey("Id")]
public virtual PdFolderTree pdFolderTree { get; set; }
}
public class PdReference
{
public int Id { get; set; }
public int ModelId { get; set; }
public string ModelCode { get; set; }
public string ReferenceCode { get; set; }
public string ReferenceName { get; set; }
public string ReferenceComment { get; set; }
public string ReferenceDescription { get; set; }
public string ReferenceAnnotation { get; set; }
public string ReferenceStereotype { get; set; }
public int ParentModelId { get; set; }
public string ParentModelCode { get; set; }
public string ParentTableCode { get; set; }
public int ParentTableId { get; set; }
public int ChildTableId { get; set; }
public string ChildTableCode { get; set; }
public string Cardinality { get; set; }
public DateTime PdInsertedDate { get; set; }
[ForeignKey("ModelId")]
public PdModel PdModels { get; set; }
public ICollection<PdJoin> pdJoins { get; set; }
[ForeignKey("ChildTableId")]
public virtual PdTable pdChildTable { get; set; }

You cannot filter an eagerly loaded relationship. The error you're getting is due to Include needing to be passed a valid property expression, which a Where clause is not.
If you only want to load a subset of this particular relationship, you'll need to explicitly load it. For example:
IQueryable<PdModel> PdModelsQuer = _context.PdModel.Where(x => x.Id == id)
.Include(x => x.PdTables)
.Include(x => x.pdFolderTree)
// remove this .Include(x => x.PdReferences.Where(y=>y.ReferenceName.Contains(searchString)))
.Include(x => x.pdViews)
.Include(x => x.pdDomains)
.Include(x => x.PdModelSources)
.Include(x => x.pdModelExtendeds)
.Include(x => x.pdRules);
foreach (var pdModel in PdModelsQuer)
{
var pdReferences = await _context.Entry(pdModel).Collection(x => x.PdReferences).Query()
.Where(x = x.ReferenceName.Contains(searchString)).ToListAsync();
}
If it's not obvious, this means issuing N+1 queries, where N is the count of your PdModels. In other words, the filtered collection has to be fetched for each instance individually.
However, based on querying by id, it appears that you should only have one matching PdModel. As such, you really shouldn't be using a Where here. Instead. Just add all your includes and then use SingleOrDefaultAsync:
var pdModel = await _context.PdModel
.Include(x => x.PdTables)
.Include(x => x.pdFolderTree)
.Include(x => x.pdViews)
.Include(x => x.pdDomains)
.Include(x => x.PdModelSources)
.Include(x => x.pdModelExtendeds)
.Include(x => x.pdRules)
.SingleOrDefaultAsync(x => x.Id == id);
Then, you can fetch the PdReferences for just this one instance:
var pdReferences = await _context.Entry(pdModel).Collection(x => x.PdReferences).Query()
.Where(x = x.ReferenceName.Contains(searchString)).ToListAsync();
It's important to note that this is being stored in another variable. Setting the filtered collection directly to your PdReferences property can cause side-effects, particularly if you end up trying to save this entity later, namely removing anything not in the filtered list from the database. In a situation like this, it's best to employ a view model and map over the data accordingly.

Related

Entity Framework retrieve data from many to many relationship

I have two tables - Order and MenuItem with many-to-many relation.
public class Order
{
[Key]
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public string Comments { get; set; }
public string OrderStatus { get; set; }
public string WaiterName { get; set; }
public double TotalPrice { get; set; }
public virtual ICollection<MenuItem> MenuItems { get; set; }
}
public class MenuItem
{
[Key]
public int MenuItemId { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int MenuItemTypeId { get; set; }
public MenuItemType MenuItemType { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class OrderMenuItem
{
public int MenuItemId { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public MenuItem MenuItem { get; set; }
}
modelBuilder.Entity<MenuItem>()
.HasMany(m => m.Orders)
.WithMany(o => o.MenuItems)
.UsingEntity<OrderMenuItem>(
x => x.HasOne(x => x.Order)
.WithMany().HasForeignKey(x => x.OrderId),
x => x.HasOne(x => x.MenuItem)
.WithMany().HasForeignKey(x => x.MenuItemId)
);
The question is how can I retrieve MenuItems that are in certain order and show the data using the ListView?
Just use the navigation property.
The simple and easy way would be
var order = await _context.Orders
.Where(o => ...)
.Include(o => o.MenuItems)
.FirstOrDefaultAsync();
and the proper way would be, one that also lets you filter and order the items would be
var order = await _context.Orders
.Where(o => ...)
.Select(o => new OrderDto {
Id = o.Id,
Status = o.Status,
// etc
Items = o.Items
.Where(i => ...)
.OrderBy(i => ...)
// etc
})
.FirstOrDefaultAsync();
And while we're at it, delete that virtual modifier. It's used for lazy loading, and what you want here is not lazy loading.
Just to tack on to what Angius posted:
var orderedMenuItems = dbContext.MenuItems.OrderBy(mi => mi.id).ToList();
Then just connect your list to your UI.

How to construct a specific linq query?

I have three tables, doctor, office and appointment. Office table has DoctorId as a foreign key, and Apponitment has a foreign key OfficeId. I want to fetch all the appointments that have OfficeId equal to Ids in the list of offices that have the same doctorId. Specifically, I don't know how to extract ids from the list of offices. Here is my code, I skipped some parts for brevity:
public class Appointment1 : BaseEntity
{
public int? Patient1Id { get; set; }
[ForeignKey("Patient1Id")]
public Patient1 Patient { get; set; }
public int Office1Id { get; set; }
[ForeignKey("Office1Id")]
public Office1 Office { get; set; }
[DataType(DataType.Date)]
public DateTime StartDateAndTimeOfAppointment { get; set; }
[DataType(DataType.Date)]
public DateTime EndDateAndTimeOfAppointment { get; set; }
public bool? Status { get; set; }
public string Remarks { get; set;}
}
public class Doctor1 : BaseEntity
{
public int ApplicationUserId { get; set; }
[ForeignKey("ApplicationUserId")]
public ApplicationUser ApplicationUser { get; set; }
public string Name { get; set; }
public string Resume { get; set; }
}
public class Office1 : BaseEntity
{
public int Doctor1Id { get; set; }
[ForeignKey("Doctor1Id")]
public Doctor1 Doctor { get; set; }
public decimal InitialExaminationFee { get; set; }
public decimal FollowUpExaminationFee { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public async Task<List<Appointment1>> GetAppointmentsWithSearchingAndPaging(QueryParameters queryParameters,
int userId)
{
var doctor = await _context.Doctors1.Where(x => x.ApplicationUserId == userId)
.FirstOrDefaultAsync();
var office = await _context.Offices.Where(x => x.Doctor1Id == doctor.Id)
.FirstOrDefaultAsync();
IQueryable<Appointment1> appointment = _context.Appointments1.Include(x => x.Patient)
.Where(x => x.Office1Id == office.Id)
.AsQueryable().OrderBy(x => x.Id);
if (queryParameters.HasQuery())
{
appointment = appointment
.Where(x => x.Office.Street.Contains(queryParameters.Query));
}
appointment = appointment.Skip(queryParameters.PageCount * (queryParameters.Page - 1))
.Take(queryParameters.PageCount);
return await appointment.ToListAsync();
}
The problem is with office which gives firstordefaultasync, and should give list, because I want all the ids, but in the end I get only appointments that have one identical officeid as a foreign key...thanks in advance!
Here is the answer, I needed this part of code, my question was not precise so I appologize:
var offices = await _context.Offices.Where(x => x.Doctor1Id == doctor.Id)
.ToListAsync();
IEnumerable<int> ids = offices.Select(x => x.Id);
IQueryable<Appointment1> appointment = _context.Appointments1.Include(x => x.Patient)
.Where(x => ids.Contains(office.Id))
.AsQueryable().OrderBy(x => x.Id);

How do I add/update the child of a child in C# using Entity Framework Core?

This answer was useful in updating child entities when a parent is updated. I'm now trying to also update a child of a child. The below works for finding what child records, if any, belong to a parent so records can be added/updated.
var calFile = await innerContext.Calibrate
.Where(x => x.DataId == dataFile.Id)
.Include(x => x.Symptom)
.SingleOrDefaultAsync();
This does not work for adding/updating the child of a child:
var calFile = await innerContext.Calibrate
.Where(x => x.DataId == dataFile.Id)
.Include(x => x.Symptom)
.Include(x => x.SymptomQuestions
.SingleOrDefaultAsync();
My entities are as follows:
public class Calibrate : IAnalyticsSection
{
public int Id { get; set; }
public Guid DataFileId { get; set; }
public bool TestType { get; set; }
public decimal Height { get; set; }
public decimal CalibratedHeadPosition { get; set; }
public decimal LeftHandPositionTPose { get; set; }
public decimal RightHandPositionTPose { get; set; }
public decimal LeftHandSpherePos { get; set; }
public decimal RightHandSpherePos { get; set; }
public ICollection<Symptom> Symptoms { get; set; } = new List<Symptom>();
public virtual DataFile DataFile { get; set; }
}
public class Symptom
{
public int Id { get; set; }
public int CalibeId { get; set; }
public int SymptomSeverity { get; set; }
public virtual ICollection<SymptomQuestions> SymptomQuestions { get; set; } = new List<SymptomQuestions>();
public virtual Calibrate Calibrate { get; set; }
}
public class SymptomQuestions
{
public int Id { get; set; }
public int SymptomsId { get; set; }
public int Question { get; set; }
public int Answer { get; set; }
public virtual Symptom Symptoms { get; set; }
}
Calibrate can have several Symptoms, each of which will have 5 questions.
How can this be done?
Maybe it's as simple as using .ThenInclude(..):
var calFile = await innerContext.Calibrate
.Where(x => x.DataId == dataFile.Id)
.Include(x => x.Symptom)
.ThenInclude(x => x.SymptomQuestions)
.SingleOrDefaultAsync();
But maybe we need to see the definition of the relations (FluentAPI?), too?
Or your issue is related to a recent breaking change of EF Core 5.0 (Non-null reference navigations are not overwritten by queries):
.Include(...) will only set related items from the database, if your navigation property is still null. Since you initialize Symptoms and SymptomsQuestions here, .Include() might just do nothing.
Side note: Why call one entity "Symptom" (singular) and the other "SymptomQuestions" (= plural)? Both seem to represent just one instance.

I get error: Value cannot be null, Why is my ICollection always null?

this is OrganizationPhoto module
public class OrganizationPhoto
{
public int Id { get; set; }
public string Url { get; set; }
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public bool IsMain { get; set; }
public string PublicID { get; set; }
public Organization Organization { get; set; }
public int OrganizationId { get; set; }
}
this is organization module
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateCreated { get; set; }
public string PublicID { get; set; }
public User User { get; set; }
public int UserId { get; set; }
public ICollection<OrganizationPhoto> OrganizationPhotos { get; set; }
public ICollection<OrganizationHeadPhoto> OrganizationHeadPhotos { get; set; }
}
i map OrganizationPhoto in automapper with dtos
CreateMap<OrganizationPhoto, PhotosForDetailedDto>();
CreateMap<OrganizationPhoto, PhotoForReturnDto>();
CreateMap<PhotoForCreationDto, OrganizationPhoto>();
then i get organization by id like this:
Task<Organization> GetOrganizationById(int id);
and
public async Task<Organization> GetOrganizationById(int id)
{
var organization = await _context.Organizations.FirstOrDefaultAsync(p => p.Id == id);
return organization;
}
and then i try get values from ICollection OrganizationPhotos, but it is null
see image
EF never loads related entities by default. You must either eagerly or explicitly load the relationships, or set up lazy loading. The best method is to eager load, as that will then do everything in one query with appropriate joins:
var organization = await _context.Organizations
.Include(x => x.OrganizationPhotos)
.Include(x => x.OrganizationHeadPhotos)
.FirstOrDefaultAsync(p => p.Id == id);
If there's sub-relationships you also need loaded, you may need to use ThenInclude as well:
.Include(x => x.OrganizationPhotos)
.ThenInclude(x => x.SomeOtherRelationship)
You should avoid lazy-loading as it's far too easy to hammer your database, if you're not paying attention to what you're doing, but if you want them "automatically" loaded then you first need to make the properties virtual:
public virtual ICollection<OrganizationPhoto> OrganizationPhotos { get; set; }
And then enable lazy loading via:
services.AddDbContext<MyContext>(o =>
o.UseSqlServer(Configuration.GetConnectionString("Foo"))
.UseLazyLoadingProxies());

Why can I not do more than one level of include in my LINQ statement?

I am using Entity Framework 5 and I have these classes:
public partial class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
}
public partial class Topic
{
public int TopicId { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public string Name { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
Now I am trying to write a LINQ query to populate this class:
public class TopicSubTopicSelect
{
public int TopicId { get; set; }
public int SubTopicId { get; set; }
public string TopicName { get; set; }
public string SubTopicName { get; set; }
}
So far I have this:
return _subjectsRepository
.GetAll()
.Where(s => s.SubjectId == subjectId)
.Include(s => s.Topics)
.Include(s => s.Topics.) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
.AsEnumerable()
.Select(item => new TopicSubTopicSelect(item.TopicId,
item.SubTopicId,
item.Topic.Name,
item.Name))
.ToList();
But it gives me an error on the line where I put <<<<<
What I wanted was to have .Include(s => s.Topics.SubTopics)
However intellisense does not give me this as an option. Any ideas what I am
doing wrong and how I can modify the LINQ to get the data to fill the TopicSubTopicSelect class
This will give you desired result -
.Include(s => s.Topics.SelectMany(t => t.SubTopics))
Use .Select if SubTopic is a property but if is a list, use .SelectMany.
For more clarification refer to Select Vs SelectMany.
.Include(s => s.Topics.Select(t => t.SubTopics))
Use .Select() within the .Include() to get the desired join.
Making a simple project to test, I received the following:

Categories