This is my first code-first project and have never had an issue doing this in db-first, that I can remember. So, I'm either missing something obvious or there's a rule I missed somewhere. I do not want to use lazy loading, and have to think that it's not required to do this, given the many examples using eager loading.
Very simple. Parent record is Listing, child records are Bid entities:
public class Listing {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ListingID { get; set; }
[Required]
[StringLength(140)]
public string Title { get; set; }
//...etc.
public List<Bid> Bids { get; set; }
public Listing() {
Bids = new List<Bid>();
}
}
public class Bid {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BidID { get; set; }
[Required]
public decimal Amount { get; set; }
//...etc.
public int ListingID { get; set; }
[ForeignKey("ListingID")]
public Listing Listing { get; set; }
public Bid() {
Listing = new Listing();
}
}
I'm using Include to pull the child records:
using (var db = new MyDbContext()) {
var listing = db.Listings
.Where(x => x.Active == true && x.UserID == "abc123")
.Include(x => x.Bids)
.FirstOrDefault();
}
Child collection exists but is always empty (Count = 0) - it's not pulling the records. They're definitely in the tables. I can manually query this and get those bid records, no problem. Basically, I need a listing and all of its bids, similar to this:
select l.*, b.*
from Listing l
inner join Bid b on l.ListingID = b.ListingID
where l.Active = 1
and l.UserID = 'abc123'
What's missing? This article says I'm doing it right:
https://msdn.microsoft.com/en-us/data/jj574232.aspx
Figured it out. I KNEW I had done this type of relationship before. I found an older project where I did the exact same thing. All I had to do was remove the constructor in the Bid class. Apparently it goes awry if you specify both sides of the relationship? I don't know...but now it works and it's pulling the expected records. Frustrating! Seems like they could add some validation to help you from shooting yourself in the foot, so easily. If it's not valid, it'd be nice to know for certain.
Everything the same as the OP, save for this class:
public class Bid {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BidID { get; set; }
[Required]
public decimal Amount { get; set; }
//...etc.
public int ListingID { get; set; }
[ForeignKey("ListingID")]
public Listing Listing { get; set; }
//public Bid() {
// Listing = new Listing();
//}
}
Try with removing the constructor for both classes.
Related
not sure what I've done wrong. I cannot, no matter what I do, and what posts I try to follow, get EF Eager loading to work. I don't know If I have done something wrong well before hand... but nothing works. Not any style of include, or disabling lazy loading... i just cannot get it.
I have an Agent model
public class Agent
{
[Key]
public int AgentId { get; set; }
[Required]
[StringLength(50)]
public string Username { get; set; }
[Required]
[StringLength(100)]
public string FirstName { get; set; }
[Required]
[StringLength(100)]
public string LastName { get; set; }
Then a prducer model
public class Producer
{
[Key]
public int ProducerId { get; set; }
[Required]
[StringLength(254)]
public string Name { get; set; }
public int StreetNumber { get; set; }
[StringLength(254)]
public string StreetName { get; set; }
[StringLength(254)]
public string City { get; set; }
public int StateId { get; set; }
public virtual State State { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
And then i have another model/table to link the two
public class AgentProducer
{
public int AgentProducerId { get; set; }
public int ProducerId { get; set; }
public int AgentId { get; set; }
public virtual Producer Producer { get; set; }
public virtual Agent Agent { get; set; }
}
The goal here, would be that when i Query thr AgentProducer table, my Producer property would have the entire producer object, and that producer object would have the Country and State objects.
I dont think this matters but the producer does have FK constraints on the countryId and stateId
My setup was from a lesson i was following, where i have a repository wrapper holding everything
public class RepositoryContext : DbContext
{
public RepositoryContext(DbContextOptions<RepositoryContext> options) : base(options)
{
}
public DbSet<Producer> Producers { get; set; }
public DbSet<Agent> Agents { get; set; }
}
The above item is held in a repository wrapper, that has a repository implementing a specific interface for each model type in it.
The problem Im having is whether i query with method syntax or LINQ, i cannot get everything to be included in the query, and end up having to do a bunch of extra steps to get certain info.
So far the closest i got was with LINQ, in which I would get the AgentProducer item to return with the Producer object set - however inside that producer, the COuntry and State were null, and I need that info.
I tried things like:
*** (Worth noting "AgentProducer" here is a DBSet... not sure if thats correct or not? I dont seem to have options like "ThenInclude" that ive seen in other answers.
_repoWrapper.Repo.AgentProducer.Include(i => i.Producer).Where(i => i.AgentId == filter.AgentId);
This gives me absolutely nothing - even the producer is null - before even making it to my country/state problem.
I tried
var res = _repoWrapper.Repo.AgentProducer.Include("Producer").Where(i => i.AgentId == filter.AgentId);
Same null result.
(from ap in _repoWrapper.Repo.AgentProducer.Include(i => i.Producer)
where ap.AgentId == filter.AgentId
select ap);
Same null.
The only thing that has even minorly worked was:
var res = (from ap in _repoWrapper.Repo.AgentProducer
join p in _repoWrapper.Repo.Producers on ap.ProducerId equals p.ProducerId
join a in _repoWrapper.Repo.Agents on ap.AgentId equals a.AgentId
join c in _repoWrapper.Repo.Country on ap.Producer.Country.Name equals c.Name
join s in _repoWrapper.Repo.State on ap.Producer.State.Name equals s.Name
where ap.AgentId == filter.AgentId
orderby p.Name descending
select new AgentProducer
{
AgentProducerId = ap.AgentProducerId,
Producer = p
});
Which fills out the producer, because of the manual join and set on the object. However, A) This isnt really eager loading, and B) using this method I have no idea how i can set the country and state objects on the producer here, as they still show null. And in the object initializer i cant just assign the country and state.
Ive browsed countless approaches and I cannot get a single thing to work... so i feel like I have done something wrong earlier in the process but I have no clue what.
Can anyone help me out?
Looks like it was me being a tool.
I had not grabbed the nuget package for EntityFrameworkCore.Relational, and had not added
using Microsoft.EntityFrameworkCore;
instead I had:
using System.Data.Entity;
I'm using MVC, C# and EntityFramework.
I've seen different solutions on Many to Many joins and after a lot of tinkering I got it to work in Linqpad. But when I try it in my solution I get an error because one of the tables isn't in my DBContext.
I have two visible tables and one hidden. Items, Recipes & RecipeItems.
All recipes are based on one item and use two or more items to be made.
So I want a list, IEnumerable or similar with the data from both Items and Recipes that specifies this recipe and then I want all the items needed to make the recipe.
The following query works in LinqPad
var t = from r in Recipes
join i in Items on r.ItemId equals i.Id
select new {FinalProduct = r.FinalProduct, Effect= i.Effect,
Description = r.Description, Ingredients = r.RecipeItems.Select(g => g.Item)};
When I do this in my solution I get the error since my DBContext only contains Recipe and Items but no RecipeItems. Entityframework handles this without me I guess.
I tried to make a DbSet<RecipeItems> without any luck. Any of you who have a suggestion of how I can move forward.
Item Class
public class Item
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Effect { get; set; }
public bool Published { get; set; }
public virtual ICollection<Recipe> Recipe { get; set; }
}
Recipe Class
public class Recipe
{
[Key]
public int Id { get; set; }
public int ItemId { get; set; }
[Display(Name = "Final Product")]
public string FinalProduct { get; set; }
public string Description { get; set; }
public RecipeGroup RecipeGroup { get; set; }
public bool Published { get; set; }
public virtual ICollection<Item> Ingredients { get; set; }
}
The ItemId in Recipe is to set the actual Item the Recipe will make.
Try adding this to your Recipe object:
public Recipe()
{
this.Ingredients = new HashSet<Item>();
}
This overrides the default constructor for the class and kind of gives EF a place that initializes the related objects.
Projecting self referencing multi level entities in Entity Framework 6.
Let's say that I have a Category entity as follows:
public class Category
{
public int CategoryId { get; set; }
public int? ParentCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
public virtual ICollection<Product> Products { get; set; }
public Category()
{
SubCategories = new HashSet<Category>();
Products = new HashSet<Product>();
}
}
And I would like to map the whole Category DbSet with all the hierarchy to a following POCO class (while including all possible levels of sub and parent categories):
public class CategoryView
{
public int Id { get; set; }
public int? ParentCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public CategoryView ParentCategory { get; set; }
public List<CategoryView> SubCategories { get; set; }
public int ProductCount { get; set; }
public Category()
{
SubCategories = new HashSet<CategoryView>();
}
}
Please bear in mind that a single category may have unlimited levels of subcategories as follows:
Category (Level 0)
SubCategory1 (Level 1)
SubCategory2
SubCategory2SubCategory1 (Level 2)
SubCategory2SubCategory2
SubCategory2SubCategory2SubCategory1 (Level 3)
... (Level N)
SubCategory3
When tried to create hierarchy with recursive a method which tries to process every single categories sub and parent categories, got stackoverflow exception, since it get stuck between the first category (Category) and the first subcategory (SubCategory1) due to relation between ParentCategory and SubCategories.
What is the best and elegant way of doing such projection (without eliminating parents)? (Or is there any?)
Any help would be appreciated.
Thank you,
I can't say if it's the best or elegant way, but it's pretty standard and efficient non recursive way of building such structure.
Start with loading all categories without parent / child object links using a simple projection:
var allCategories = db.Categories
.Select(c => new CategoryView
{
Id = c.CategoryId,
ParentCategoryId = c.ParentCategoryId,
Name = c.Name,
Description = c.Description,
ProductCount = c.Products.Count()
})
.ToList();
then create a fast lookup data structure for finding CategoryView by Id:
var categoryById = allCategories.ToDictionary(c => c.Id);
then link the subcategories to their parents using the previously prepared data structures:
foreach (var category in allCategories.Where(c => c.ParentCategoryId != null))
{
category.ParentCategory = categoryById[category.ParentCategoryId.Value];
category.ParentCategory.SubCategories.Add(category);
}
At this point, the tree links are ready. Depending of your needs. either return the allCategories or the root categories if you need a real tree representation:
return allCategories.Where(c => c.ParentCategoryId == null);
P.S. Actually the allCategories list can be avoided, since categoryById.Values could serve the same purpose.
It might not be elegant, but a suitable solution is to have in your code a shared IDictionary<int, CategoryView>. When you are going to map an entity Category into a CategoryView check first if you have already created this object and set the reference stored in the dictionary instead of creating a CategoryView instance. When creating a new instance, store it in the dictionary. This is a way to take advantage of the primary key of your entity to avoid the infinite recursion issue in your code.
Also, notice that in your CategoryView object you shouldn't be referencing Category instances. Update it to reference CategoryView instances like this.
public class CategoryView
{
public int Id { get; set; }
public int? ParentCategoryId { get; set; }
// other properties ...
public CategoryView ParentCategory { get; set; }
public List<CategoryView> SubCategories { get; set; }
public int ProductCount { get; set; }
public CategoryView()
{
SubCategories = new List<CategoryView>();
}
}
I'm begginer with EF, so my question is probably basic, but I couldn't find any answer...
I have a SQL Compact DB, from which I generated an entity model via the VS wizard. Everything seems fine, I retrieve all my relationships with the good mapping.
So as I understand from here: http://msdn.microsoft.com/en-us/library/bb386932(v=vs.110).aspx I should be able to do this, "querying across relationships":
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.ID == ProdID
select i;
But I get the following error:
'System.Collections.Generic.ICollection' does not
contain a definition for 'ID' and no extension method 'ID' accepting a
first argument of type
'System.Collections.Generic.ICollection' could be found
(are you missing a using directive or an assembly reference?).
This error occurs when you try to call a method or access a class member that does not exist
However, if I go deeper into the auto-generated code, I can see a public property 'ID' is declared for 'Product', and 'Ingredient' return a collection of 'Product':
Ingredient
public partial class Ingredient
{
public Ingredient()
{
this.Product = new HashSet<Product>();
}
public string Name { get; set; }
public int ID { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
Product
public partial class Products
{
public Products()
{
this.Ingredient = new HashSet<T_PROPSTHERAP>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Usage { get; set; }
public byte[] Photo { get; set; }
public int FK_OrganeProduct { get; set; }
public int FK_Type { get; set; }
public virtual OrganeProduct OrganeProduct { get; set; }
public virtual Type Type { get; set; }
public virtual ICollection<Ingredient> Ingredient { get; set; }
}
But it doesn't work as I expected.
I can use the following as workaround:
List<Ingredient> lstIng = (_oTest.Products
.Where(p => p.Name == (string)lsbProducts.SelectedItem)
.SelectMany(p => p.T_PROPSTHERAP)).ToList();
But I don't think it's a smart way to do the trick... And I don't understand what I am missing...
Could anyone help?
If I understand you correctly, you are trying to find Ingredients based on Product's ID. As you have known, the Product property is a collection, not a singular object.
What you need is filtering Products based on Product's ID, you can use Any to filter collection.
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.Any(p => p.ID == ProdID)
select i;
That means:
Looking for Ingredient if any of its product has ID equals to ProdID.
You can also use All, if what you are looking for is:
Looking for Ingredient if all of its products have ID equals to ProdID.
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.All(p => p.ID == ProdID)
select i;
PS
However, based on your workaround, using Any is what you are looking for.
My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away.
I have a User which has a list of UserContact's which itself has a ContactOption. Its a fairly simple 1 to many, many to 1 with the UserContact table in the middle.
If I pull the user out of the db and create a new UserContact, but set the ContactOption to an existing item (which I've pulled out of the db), when I SaveChanges, entity framework creates a new ContactOption in the database that is essentially a duplicate of the one I added to the UserContact (with the exception that it gets a new id).
I've battled with this for several hours and can't figure this out. Any ideas?
I am using a Repository pattern for my database queries, but I have ensured they are sharing the same context.
I pull the user out of the database with this:
var user = _context.Users.Include("UserContacts.ContactOption")
.Where(id => id == 1);
And contact options are pulled out with:
var co = _context.ContactOptions.FirstOrDefault(c => c.Id == id);
And I add the ContactOption to the UserContact like so:
var contactOption = _context.ContactOptions.FirstOrDefault(c => c.Id == someId);
var contact = new UserContact { ContactOption = contactOption };
contact.Data = "someData";
user.UserContacts.Add(contact);
My model looks like this:
public class User
{
[Key]
public int Id { get; set; }
public virtual ICollection<UserContact> UserContacts { get; set; }
}
public class UserContact
{
[Key]
public int Id { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
public class ContactOption
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
I run your code and got the exact expected results: A new row in UserContacts table with the existing UserId and ContactOptionId, so I am not sure what's happing in there, but you can try to explicitly have FKs in UserContact object so that you'll have full control over how Code First inserts records for you. For that, you need to change UserContact as follows:
public class UserContact
{
public int Id { get; set; }
// By Convention these 2 properties will be picked up as the FKs:
public int UserId { get; set; }
public int ContactOptionId { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
And then you can change your code like this:
var contactOption = _context.ContactOptions.Find(someId);
var user = _context.Users.Find(1);
var contact = new UserContact
{
ContactOptionId = contactOption.Id,
UserId = user.Id,
Data = "someData"
};
user.UserContacts.Add(contact);
context.SaveChanges();
My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away. Thanks to Morteza Manavi for pointing me in the right direction.