I'm trying to setup a method that returns all presentations that are not overlapping with presentations you have signed up for. However, when trying to implement this I came across an error I can't seem to fix, I looked around and haven't been able to find any similar issues. Am I missing something obvious?
This is the Error:
InvalidOperationException: variable 't0' of type
'Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[NameSpace.Models.Presentation,Microsoft.EntityFrameworkCore.Storage.ValueBuffer]'
referenced from scope '', but it is not defined
These are my models.
public class ApplicationUser : IdentityUser
{
public List<Presentation> MyPresentations { get; set; }
public List<PresentationUser> RegisteredPresentations { get; set; }
}
public class Presentation
{
public int PresentationId { get; set; }
public string HostId { get; set; }
public ApplicationUser Host { get; set; }
public List<PresentationUser> Attendees { get; set; }
public int TimeId { get; set; }
public PresentationTime Time { get; set; }
}
public class PresentationUser
{
public int PresentationId { get; set; }
public Presentation Presentation { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
public class PresentationTime
{
public int PresentationTimeId { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
This is the method I can't get to work
private async Task<IQueryable<Presentation>> GetAvailablePresentations()
{
User user = await context.Users
.Include(u => u.RegisteredPresentations)
.ThenInclude(eu => eu.Presentation.Host)
.FirstOrDefaultAsync(u => u.Id == userManager.GetUserId(User));
var Presentations = context.Presentations
.Include(e => e.Host)
.Include(e => e.Time)
.Include(e => e.Attendees)
.ThenInclude(e => e.ApplicationUser)
// Filter Out Conditions
.Where(e => e.Attendees.All(u => u.ApplicationUserId != user.Id)) // Cannot see Presentations they are attending.
.Where(e => e.HostId != user.Id); // Cannot see their own Presentation
var debug = user.RegisteredPresentations.Select(ex => ex.Presentation).ToList();
// This section makes it so that users can't sign up for more that one Presentation per timeslot.
// Error Occurs Here
Presentations = Presentations.Where(e => debug.All(ex =>
ex.Time.EndTime < e.Time.StartTime || e.Time.EndTime < ex.Time.StartTime));
// This also does not work
// Presentations = Presentations.Where(e => debug.All(ex => ex.Time.StartTime != e.Time.StartTime));
return Presentations;
}
If anyone can help me fix this it would be huge help.
Note: I stripped a lot of other logic to help isolate this issue, so I may have a couple unnecessary .Include() in this code.
Presentations is not a list, it's still a IQueryable - a not-yet-executed query to DB. Applying Where you instruct EF to apply additional WHERE in SQL.
But debug is a list of objects in memory (.ToList()). How you think EF will transfer them back to DB?
If you want all filtering be applied in DB - you should change debug to list of something "simple" (list of ids?) - then EF will be able to pass this list back to DB.
Alternatively, you should read all suitable Presentations into memory (call .ToList()) and apply last filtering in memory. You may calculate min(StartTime) and max(EndTime) from debug and apply this two simple values to Presentations query (you will receive less unnecessary items) then read to memory and apply "strong" filtering in memory.
Related
I am a little puzzled as to why this code doesn't work. I have a basic one-to-many relationship where I load a parent and include its children. I later I am trying to navigate from the child back to the parent, but the parent is null and I can't figure out why.
Question:
Why can't I query a graph of parent/child objects and navigate backward through them from child to parent? The parent is always null.
Here are the entities.
public class Budget
{
[Key]
public int Id { get; set; }
public ICollection<Expense> Expenses { get; set; }
public ICollection<Income> Incomes { get; set; }
}
public class Expense
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string ExpenseName { get; set; }
[Required]
public decimal Cost { get; set; }
[StringLength(800)]
public string Notes { get; set; }
public DateTime? DueDate { get; set; }
public Budget Budget { get; set; }
}
public class Income
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public decimal Amount { get; set; } = 0;
[Required]
[StringLength(200)]
public string Source { get; set; }
[Required]
public DateTime? PayDate { get; set; } = DateTime.Now;
public Budget Budget { get; set; }
}
Here is the repository query.
public async Task<Budget> GetBudget(int id)
{
try
{
return await context.Budget
.Include(e => e.Expenses)
.Include(i => i.Incomes)
.SingleAsync(b => b.Id == id);
}
catch(Exception x)
{
x.ToString();
}
return null;
}
I want to be able to navigate back through the relation from expense to budget to get the Budget.Income collection.
Expected result:
foreach(Expense expense in Budget.Expenses)
{
if (expense.Budget is not null)
{
ICollection<Income> paychecks = expense.Budget.Incomes; // Why is Budget always null?
}
}
I expected that even if I didn't use the ThenInclude(e => e.Budget) that I should still be able to navigate from the child back to the parent {var budget = expense.Budget}. I'm surprised that this isn't working.
I didn't include the Income entity here, but my goal is to traverse expense.Budget.Incomes to get the collection of incomes in code where I only have access to the expense instance.
After removing ThenInclude(e => e.Budget) I no longer get an error, but the expense.Budget property is still null.
UPDATE
I believe that I found the root cause of my problem. When I added the property Budget to the Expense class I started getting an error when deserializing the objects coming from the API. The HttpClient was throwing an error due to a cyclical reference.
Because I'm fetching the root Budget entity and it's related Expenses, and the Expense has a reference to the Budget I got the cyclical reference error.
I added this code the the startup Blazor server startup class to fix it. I think this is my problem.
services.AddControllersWithViews().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
If I change to ReferenceHandler.Preserve I get a different error.
'The JSON value could not be converted to System.Collections.Generic.ICollection`1[BlazorApp.Data.Models.Expense]. Path: $.expenses | LineNumber: 0 | BytePositionInLine: 34.'
What I don't know and would like to solve is how to make this work so I can get the Budget -> Expenses and have the Expense.Budget property point to it's parent Budget instance. My real issue is probably more related to json serialization and deserialization.
Thank you all for the help. Your input helped me know what wasn't the problem.
This fixed my problem.
WebAPI : JSON ReferenceHandler.Preserve
My original question doesn't relate to the actual answer. I did not realize what was really going on. The real problem was a serialization problem. I'm using a hosted Blazor app and have Client and Server projects.
The problem has a number of layers to it. I added the Budget property to the Expense class so that I could traverse back to the budget. After adding the Budget property I started getting an error about cyclical references. This error appears in the Client project during the HttpClient call to the Server controller. I fixed this error by adding the following code to the Server Startup class.
services.AddControllersWithViews().AddJsonOptions(options => {
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
options.JsonSerializerOptions.PropertyNamingPolicy = null; // prevent camel case
});
Days later when I start doing more work I tried to reference the expense.Budget property. Finding that it was always null and started trying to fix it in the entities which is the wrong place.
It turns out that I also needed to pass the JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve to the HttpClient code during the call to the controller.
public async Task<Budget> GetBudget(int id)
{
Budget budget = null;
try
{
budget = await httpClient.GetFromJsonAsync<Budget>("api/Budget/" + id, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve });
}
catch(Exception x)
{
x.ToString();
}
return budget;
}
This foreach statement now works as expected.
foreach(Expense expense in Budget.Expenses)
{
if (expense.Budget is not null)
{
ICollection<Income> paychecks = expense.Budget.Incomes; // How do I populate budget?
}
}
You have a bug in your query. Income doesn't depend on expenses, it depends only on budget. The same is about expenses.
So your code can be translated to this
var budget= await context.Incomes
.Include(e => e.Expenses)
.Include(i => i.Incomes)
.SingleAsync(b => b.Id == id);
foreach (Expense expense in budget.Expenses)
{
var paychecks = budget.Incomes;
}
This foreach doesn't make any sense in this case since it just repeats
"var paychecks = budget.Incomes;" many times. It is not even saved anywhere.
The same can be done in one line
var paychecks= await context.Incomes
.Where(b => b.BudgetId == id);
.ToListAsync();
or if budget is downloaded already
var paychecks = budget.Incomes;
You can not use .ThenInclude(e => e.Budget) since your are querying context.Budget already and as you can see the Budget class doesn't have any extra Budget property.
And Expenses are list. List don't have any Budget property too, only items of the list have. If you try .Include(e => e.Expenses.Budget) it will give a syntax error. But since Expense or Income class has a Budget property this query will be valid
return await context.Incomes
.Include(i => i.Budget)
....
I think you need only this query that merges budget, income and expenses together
var budget= await context.Budgets
.Include(e => e.Expenses)
.Include(i => i.Incomes)
.FirstOrDefaultAsync(b => b.Id == id);
Also remove public ICollection<Expense> Expenses { get; set; } from Income or make it [NotMapped] if you need it for some reasons.
If you use EF Core 5+, EF Core automatically created foreign keys as the shadow properties. But I personally prefer to have them explicitly.
I recommend to add them to your classes (or you still can keep the current version)
public class Budget
{
[Key]
public int Id { get; set; }
.............
[InverseProperty("Budget")]
public virtual ICollection<Expense> Expenses { get; set; }
[InverseProperty("Budget")]
public virtual ICollection<Income> Incomes { get; set; }
}
public class Expense
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
...........
public int BudgetId { get; set; }
[ForeignKey(nameof(BudgetId ))]
[InverseProperty("Expenses")]
public virtual Budget Budget { get; set; }
}
public class Income
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
............
public int BudgetId { get; }
[ForeignKey(nameof(BudgetId ))]
[InverseProperty("Expenses")]
public virtual Budget Budget { get; set; }
}
I have a .net core api application which includes EF to retrieve data. I have set up a data context and I can map tables from the db fine. When I try and set up a relationship though I am always getting a null back for the nested object.
I have an 'Opportunity' class which contains an ICollection of 'Notes'
public class Opportunity
{
public int Id { get; set; }
public string Name { get; set; }
...
public decimal FinalDealProfit { get; set; }
public ICollection<CRMNote> CRMNotes { get; set; }
}
and a Note class that references the opportunity:
public class CRMNote
{
public int Id { get; set; }
public int OpportunityId { get; set; }
public string Note { get; set; }
public string User { get; set; }
public DateTime DateTime { get; set; }
public string FilePath { get; set; }
public Opportunity Opportunity { get; set; }
}
In my context class have the following set up:
modelBuilder.Entity<Opportunity>(entity =>
{
entity.ToTable("CRM_Opportunity");
entity.HasMany<CRMNote>(n => n.CRMNotes)
.WithOne(t => t.Opportunity)
.HasForeignKey(k => k.OpportunityId);
});
and I have also been mapping the Note class:
modelBuilder.Entity<CRMNote>(entity =>
{
entity.ToTable("CRM_Note");
//entity.HasOne<Opportunity>(t => t.Opportunity)
// .WithMany(p => p.CRMNotes)
// .HasForeignKey(k => k.OpportunityId);
});
as you can see I have been playing around with how to connect the entities together.
Whenever I retrieve the opportunity though the notes array is always null. I have tried putting an empty constructor on the Opportunity class:
public Opportunity()
{
CRMNotes = new List<CRMNote>();
}
but this just means I get an empty array rather than a null.
I can't see what I have missed. I have checked the docs for it:
https://www.entityframeworktutorial.net/efcore/one-to-many-conventions-entity-framework-core.aspx
but clearly I have missed something. Any help greatly appreciated as this should be an easy task but something is clearly eluding me.
There are three common O/RM patterns used to load related data
Eager loading,
Explicit loading
and
Lazy loading
For example, in eager loading you can use:
var opportunities=context.opportunities.Include(opportunity=>opportunity.CRMNotes).ToList()
I have searched on google and stackoverflow for a while and yes I have found many articles on the subject but I still can't seem to know what I am doing wrong.
Problem: I do have a "link" table (many-to-many) between three tables:
- check
- car
- gate
My 'link' class looks like follows:
public class CheckCarGate
{
public int CheckId { get; set; }
public Check Check{ get; set; }
public int CarId { get; set; }
public Car Car{ get; set; }
public int GateId { get; set; }
public Gate Gate { get; set; }
}
DbContext:
public virtual DbSet<Car> Cars{ get; set; }
public virtual DbSet<Gate> Gates { get; set; }
public virtual DbSet<Check> Checks{ get; set; }
// The key:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CheckCarGate>().HasKey(p => new { p.CheckId, p.CarId, p.GateId });
}
I did follow this documentation https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many when creating models. CheckCarGate is a navigation property in the entity models. E.g.
public class Gate
{
public int GateId { get; set; }
public string Descr { get; set; }
public ICollection<CheckCarGate> CheckCarGates { get; set; }
}
Code first created this table in the database. Now I'm trying to select all Checks with all Gates for one particular CarId.
I tried something like this:
var masterlist = _context.Checks.Where(p => p.CheckCarGate.Any(x => x.CarId == 12));
or:
var masterlist = _context.Checks
.Include(p => p.CheckCarGate)
.ThenInclude(p=>p.Gate)
.Include(p=>p.CheckCarGate)
.ThenInclude(p=>p.Car);
//Edit: I'm sorry for not giving enough information! Bear with me...
Could someone point me to the right direction?
Many thanks in advance!
N.
I'm not sure there is enough information here to answer your question; it appears like each check has its own checkcargate, which doesn't really make sense.
Assuming you had a table of checks and CheckCarGate table seperatatly, you can use where and select to make this query.
var out = CheckCarGateTable.Where(t => t.CarID == targetCarID)
.Select(c => checks.getbyid(c.CheckID))
or, based on your class definition
var out = CheckCarGateTable.Where(t => t.CarID == targetCarID)
.Select(c => c.Check))
First we get all CheckCarGates that refer to our target car, then we transform these into checks using the select call; out should be of type IEnumerable< Check >
and process the list like:
foreach (Check c in out)
{
proc(c);
}
This is the best I can do with the information given.
Your OnModelCreating(...) should have more in it defining the relationship
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CheckCarGate>().HasKey(p => new { p.CheckId, p.CarId, p.GateId });
builder.Entity<CheckCarGate>().HasOne(c => c.car)
.WithMany(c => c.check)
.HasForeignKey(k => k.carId)
...
}
I'm not sure how you change your query after that, the documentation link you provided didn't have example usage.
I gave up looking for a LINQ solution. (2 days wasted).
Instead I decided to use raw sql and store the data in a new object.
Credits to: https://stackoverflow.com/users/355714/pius and his solution: https://stackoverflow.com/a/46013305/848642
I have next entity
public class Objective
{
public virtual UserInfo AssignedUser { get; set; }
public int? AssignedUserID { get; set; }
public string ObjectiveText { get; set; }
public virtual ICollection<ObjectiveTask> Tasks { get; set; }
public virtual UserInfo User { get; set; }
public int UserID { get; set; }
}
One objective could has one Assigned User and one User but many Tasks.
After getting Entity from DB I map it to DTO class which looks like this
public class ObjectiveListViewModel
{
public string AssignedString { get; set; }
public string ObjectiveText { get; set; }
public int TasksCount { get; set; }
public string UserContactName { get; set; }
}
Mapping settings doesn't meter
When I do this with query like this
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.ToListAsync();
Everything works cool - User and Assigned User properties are loaded and no need do extra query to DB to get their data.
But I need return objectives with tasks amount.
To do this I've created a generic class
public class EntitySubCount<TEntity>
{
public TEntity Entity { get; set; }
public int GroupCount { get; set; }
}
And use it in this way
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.Select(o=> new EntitySubCount<Objective> {
Entity = o,
GroupCount = o.Tasks.Count })
.ToListAsync();
But User and Assigned User properties are not loaded and it require additional query to DB to get their data.
I understand that it because lazy loading.
The question is - how I can get from DB my Entity with loaded nav. properties and with count of Tasks at once?
Thank you for any help
You are close. No need for the includes if you are projecting. In this case I project to an anonymous type, but you could create a ViewModel class to project to if desired.
var objectiveList = context.Objectives
.Select(o => new
{
Entity = o,
// or you could just pick the properties:
ObjectiveText = o.ObjectiveText,
User = o.User,
AssignedUser = o.AssignedUser,
GroupCount = o.Tasks.Count
}).ToList();
EDIT: I see you already have a ViewModel(DTO). You might be looking for something like this:
var objectiveList = context.Objectives
.Select(o => new ObjectiveListViewModel
{
AssignedString = o.AssignedUser.Name,
ObjectiveText = o.ObjectiveText,
TasksCount = o.Tasks.Count
UserContactName = o.User.Name
}).ToList();
I am using Entity Framework Core following Chris Sakell's blog here.
He uses generics to manage his repositories and also a base repository that he uses for all the other repositories.
Part of the base repository has the the following code for the retrieval of a single entity that also downloads related entities using the includeProperties option. Here is the generic code for a retrieving a single item.
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
I am using it on a client table that has many jobs attached to it.
This is how I structured my code.
public ClientDetailsViewModel GetClientDetails(int id)
{
Client _client = _clientRepository
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
if(_client != null)
{
ClientDetailsViewModel _clientDetailsVM = mapClientDetailsToVM(_client);
return _clientDetailsVM;
}
else
{
return null;
}
}
The line:
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
successfully retrieves values for creator state and job.
However, nothing is retrieved for those related entities associated with the "jobs".
In particuar, JobVisits is a collection of visits to jobs.
For completeness I am adding the "job" and "jobvisit" entities below
public class Job : IEntityBase
{
public int Id { get; set; }
public int? ClientId { get; set; }
public Client Client { get; set; }
public int? JobVisitId { get; set; }
public ICollection<JobVisit> JobVisits { get; set; }
public int? JobTypeId { get; set; }
public JobType JobType { get; set; }
public int? WarrantyStatusId { get; set; }
public WarrantyStatus WarrantyStatus { get; set; }
public int? StatusId { get; set; }
public Status Status { get; set; }
public int? BrandId { get; set; }
public Brand Brand { get; set; }
public int CreatorId { get; set; }
public User Creator { get; set; }
....
}
public class JobVisit : IEntityBase
{
...
public int? JobId { get; set; }
public Job Job { get; set; }
public int? JobVisitTypeId { get; set; }
public JobVisitType VisitType { get; set; }
}
My question is, how do I modify the repository code above and my GetSingle use so that I can also load the related enitities JobVisit collection and the other related single entities Brand and JobType?
It is intended that navigation properties are not necessary retrieved for associated with the "jobs". That is why some properties are null. By default the .Include(property); goes only 1-level deep and that is a good thing. It prevents your query from fetching all the data of your database.
If you want to include multiple levels, you should use .ThenInclude(property) after .Include(property). From the documentation:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
My advice is that your method public T GetSingle(...) is nice and I would not change it in order to include deeper levels. Instead of that, you can simply use explicit loading. From the documentation:
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}