Entity Framework - select collections along-side one entity in its collection - entity-framework

I have two entities in C#. One is Box. The other one is Ball.
A Ball must belongs to a Box. Which means that there is a BoxId on Ball.
public class Box
{
public int Id { get; set; }
public IEnumerable<Ball> Balls { get; set; }
}
public class Ball
{
public int Id { get; set; }
public int BoxId { get; set; }
public Box Box { get; set; }
public string Color { get; set; }
public DateTime CreationTime { get; set; }
}
Now I need to get some boxes, with the lastest ball inserted in it because I need to know its color.
I have tried this way:
// Runs slow because there is too many balls.
var boxes = await Boxes
.Where(somecondition)
.Include(t => t.Balls)
.ToListAsync()
foreach (var box in boxes)
{
// Get the color which I only need.
box.Balls.OrderByDesending(t => t.CreationTime).First().Color;
}
But doing this will query all balls in the database. Now there are lots of balls(about 10,000,000) so it queries slow.
I only need the last inserted box. How can I do?

You can try with Select and fetch only last record for Balls like below.
Also when we use Select we can remove .Include because it won’t have any effect here.
var boxes = await Boxes
.Where(somecondition)
.Select(t => new Box(){
Id = t.Id,
Balls = t.Balls.OrderByDescending(x => x.CreationTime)
.Take(1)
})
.ToListAsync()

Related

EF code first one to many navigate from child to parent

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

Entity Framework Core navigations not updating

I'm using Entity Framework Core v5.0.3 for this.
I'm making a simple information app for users where each user can have favourite colours.
Person model
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
public bool IsAuthorised { get; set; }
public bool IsValid { get; set; }
public bool IsEnabled { get; set; }
public virtual ICollection<FavouriteColour> FavouriteColours { get; set; }
}
Favourite colour model
public class FavouriteColour
{
[Key]
public int PersonId { get; set; }
public virtual Person Person { get; set; }
[Key]
public int ColourId { get; set; }
public virtual Colour Colour { get; set; }
}
Colour model
public class Colour
{
[Key]
public int ColourId { get; set; }
public string Name { get; set; }
public bool IsEnabled { get; set; }
public virtual IList<FavouriteColour> FavouriteColours { get; set; }
}
In my database context I've defined them as so
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FavouriteColour>()
.HasKey(c => new { c.PersonId, c.ColourId });
modelBuilder.Entity<FavouriteColour>()
.HasOne(fc => fc.Person)
.WithMany(p => p.FavouriteColours)
.HasForeignKey(p => p.PersonId);
modelBuilder.Entity<FavouriteColour>()
.HasOne(fc => fc.Colour)
.WithMany(c => c.FavouriteColours)
.HasForeignKey(c => c.ColourId);
}
Now in the app a user can add or remove favourite colours so a new user object is received in the controller which calls the repository update
public async Task<IActionResult> PutPerson(int id, Person person)
{
peopleRepository.Update(person);
}
The repository then does the update
public void Update(Person person)
{
_context.Update(person);
}
After doing await _context.SaveChangesAsync(); none of the colours change. I thought the purpose of all these models and such was so the colours would change automatically?
I setup a FavouriteColourRepository instead to do the updates like this
public async Task<bool> Update(int personId, ICollection<FavouriteColour> favouriteColours)
{
// empty their favourite colours
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId);
// add new favourite colours
_context.FavouriteColours.AddRange(favouriteColours);
return true;
}
And I changed my controller to this
public async Task<IActionResult> PutPerson(int id, Person person)
{
peopleRepository.Update(person);
bool valid = await favouriteColourRepository.Update(id, person.FavouriteColours);
}
But for some reason I can't figure out
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId);
actually alters the favouriteColours parameter and forces the last state into it which creates duplicate primary keys and the inserting fails.
So why do the new favourite colours never get inserted and why is my favouriteColours parameter being edited when I'm trying to clear all the colours a user already has?
why do the new favourite colours never get inserted
The call _context.Update(person); puts the Person entity and all is related FavouriteColour entity in Modified state. Hence, on the next SaveChanges call EF is supposed to submit update commands to the database.
If you modify any property of the Person entity, you'll find that the person has been updated correctly. The issue with the FavouriteColour entity is that it contains nothing but the primary key properties, and EF does not modify the key property (or any property that is part of a composite primary key). You can test this with the following code -
var fc = dbCtx.FavouriteColours
.FirstOrDefault(p => p.PersonId == 1 && p.ColourId == 4);
fc.ColourId = 6; // CoulourId is part of primary key
dbCtx.SaveChanges();
and you will be met with an exception -
The property 'FavouriteColour.ColourId' is part of a key and so cannot
be modified or marked as modified. ...
Therefore, EF will not even generate any update command for the FavouriteColours, even though all of them are marked as Modified.
To update the Person and all its FavouriteColour you need to do something like -
// fetch the exising person with all its FavouriteColours
var existingPerson = dbCtx.Persons
.Include(p => p.FavouriteColours)
.FirstOrDefault(p => p.Id == id);
// modify any properties of Person if you need
// replace the existing FavouriteColours list
existingPerson.FavouriteColours = person.FavouriteColours;
// save the changes
dbCtx.SaveChanges();
This will -
delete any FavouriteColour that are in the existing list but not in the new list
insert any FavouriteColour that are in the new list but not in the existing list.
With your repositories in place, its up to you how you implement this.
why is my favouriteColours parameter being edited when I'm trying to
clear all the colours a user already has
Before this operation you called _context.Update(person);. Therefore, Person is now being tracked in Modified state as an existing entity. Then when you called -
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId));
the _context.FavouriteColours.Where(fc => fc.PersonId == personId) part, fetched the existing FavouriteColours of that Person from the database. So these fetched FavouriteColours are added to the person's FavouriteColours list (because it is being tracked).
For example, lets say in your controller you received a Person entity with 4 FavouriteColour, and the database has 3 FavouriteColour for this person. After calling _context.Update(person);, the person is being tracked with a list of 4 FavouriteColour. Then when you fetched this person's existing FavouriteColours from the database, the person is being tracked with a list of total 7 FavouriteColour.
I hope that shades any light.

How to get Entity from DB including navigation properties and child list total amount

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

Why add a constraint in one to many relationship entity framework code first

I'm trying to understand the difference in my one-to-many relationship after I add a virtual call back to my main entity 'space'. All it does, looking at the tables is create a constraint. Can someone please explain the difference between having
public virtual Space Space { get; set; }
and leaving it out?
Here is my main entity 'space' and 'spaceimage' that has the virtual call back to 'space'
public class Space
{
public int SpaceId { get; set; }
public virtual ICollection<SpaceImage> Images { get; set; }
}
public class SpaceImage
{
public int SpaceImageId { get; set; }
public byte[] Image { get; set; }
public byte[] ImageThumbnail { get; set; }
public string ContentType { get; set; }
public bool IsMain { get; set; }
public virtual Space Space { get; set; } // adds a constraint in sql server
}
the difference is you can make a call like this :
var thisSpace = _context.Space.Include( x => x.Images).Where( blah == blah);
ok, so know you got your space from the database , now you don't need any other query at all to get images, you would just do.
thisSpace.Images // now this is filled with a collection of images
You do not need a join , you don't need to do anything at all - see how awesome Entity Framework is. The down side... is the is some overhead in a framework that can do stuff like this.
UPDATE:
you asked about Lazy Loading , you still have control of lazy loading or not.
See if you were to do :
var thisSpace = _context.Space.Include( x => x.Images).Where( blah == blah).ToList();
then the query will execute right away and the Images will be attached. or..
you could do something like
var thisSpace = _context.Space.Include( x => x.Images).Where( blah == blah);
foreach( var image in thisSpace.Images){
// when you hit this in code the query will actually execute
// if lazy loading
}

Entity Framework - Adding parent is also trying to add child when I don't want it to

I have two objects (WishListItem and Product) in a one-to-many relationship. WishListItem has to have a product. Each Product can be in 0 - n WishListItems.
public class WishListItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
// ... other properties
}
The Product has no knowledge of WishListItems. All of the Products exist. I just want to add a new WishListItem. My WishListItem model for the relationship is this:
HasRequired(p => p.Product).WithMany().HasForeignKey(p => p.ProductId);
When I try to add a new item like this:
WishListItem item = new WishListItem();
// ... sets properties
WishListItems.Add(item); // WishListItems is of type DbSet<WishListItem>
SaveChanges();
This code seems to try to also add a Product. I don't want to add a new Product (or even update it). The Product property is set to null. How do I tell Entity Framework that I only want to add the WishListItem? Do I need to Ignore the Product property (by doing Ignore(p => p.Product); in my WishListItem model) and load the Product separately whenever I load my WishListItem objects?
I have solved my issue. The problem came from another property on the Product object.
private bool _isFreeShippingInitialValue;
public bool IsFreeShipping
{
get
{
return _isFreeShippingInitialValue ||
computedValueFromAnotherChildObject;
}
set
{
_isFreeShippingInitialValue = value;
}
}
We noticed that when you first get the Product object, the IsFreeShipping.get is called (not sure why) before any child objects are loaded. For example, if _isFreeShippingInitialValue is false and computedValueFromAnotherChildObject is true, IsFreeShipping first returns false (because computedValueFromAnotherChildObject is first false since no child objects have been loaded), then true the next time you try to get IsFreeShipping. This makes EF think the value has changed.
The first item we added to WishListItems worked fine. It was the second item that broke. We believe SaveChanges() (or something prior to it) loaded the Product for the first WishListItem. The SaveChanges() was breaking on the Product of the first WishListItem when we were adding the second item.
So, in short, be careful when computing values in a Property.get using child objects because it can bite you in the butt.
This works for me without adding any new Addresses records. In this model, Person has an optional home address, but address doesn't have any knowledge of the person.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Address HomeAddress { get; set; }
public int HomeAddress_id { get; set; }
}
public class Address
{
public int Id { get; set; }
public string PhoneNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
In the DbContext override, I have the below code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(t => t.HomeAddress).WithMany()
.HasForeignKey(t => t.HomeAddress_id);
}
I can write a unit test like this.
var addressId = 0;
using (var db = new DataContext())
{
var address = new Address { City = "test", Country = "test", PhoneNumber = "test", State = "test", Street = "test" };
db.Addresses.Add(address);
db.SaveChanges();
addressId = address.Id;
}
using (var db = new DataContext())
{
var person = new Person { Email = "test#test.com", FirstName = "Testy", LastName = "Tester", HomeAddress_id = addressId };
db.Persons.Add(person);
db.SaveChanges();
}