Lazy Loading not working, related entity is always null - c#

I Have a problem with the EF.
The realated entity is always null. I didn't get any solutions so far.
Here are the models:
public class Categories
{
public int ID { get; set; }
public string Name { get; set; }
public int AtpID { get; set; }
public virtual ICollection<SubCategories> SubCategories { get; set; }
}
public class SubCategories
{
public int ID { get; set; }
public string Name { get; set; }
public int CategoryID { get; set; }
public string LinkToProducts { get; set; }
}
So, every Categorie has more subcategories.
In the Seed method I populated the database, so I have data:
var categories = new List<Categories>
{
new Categories{Name="Abgasanlage", ID=1},
new Categories{Name="Elektrik",ID=2},
new Categories{Name="Filter", ID=3},
new Categories{Name="Karosserie", ID=4},
new Categories{Name="Kuhlunkg",ID=5}
};
categories.ForEach(s => context.Categories.Add(s));
context.SaveChanges();
var subCategories = new List<SubCategories>
{
new SubCategories{Name="Montageteile", ID=1, CategoryID=1},
new SubCategories{Name="Lamdasonde",ID=2, CategoryID=1},
new SubCategories{Name="Anlasser", ID=3, CategoryID=2},
new SubCategories{Name="Luftfilter", ID=4, CategoryID = 3},
new SubCategories{Name="Ohlfilter", ID=5, CategoryID = 3},
new SubCategories{Name="Sonstige", ID=6, CategoryID = 4},
new SubCategories{Name="Wasserpumpe", ID=7, CategoryID = 5}
};
subCategories.ForEach(s => context.SubCategories.Add(s));
context.SaveChanges();
Altought it seems that evrything is ok, the related entity is always null, even with the Include(), is null.
I tried this way:
Models.Categories entity = db.Categories.Where(m => m.ID == 3)
.Include(m => m.SubCategories)
.FirstOrDefault();
but entity.SubCategories is always null.
with Include also the related entity is null
var setting = (from s in db.Categories.Include("SubCategories")
where s.ID == 3
select s).FirstOrDefault();
In my project I have more related entities, where the lazy loading is working.
Only with these models (Categories and SubCategories) I have the problem.
What I'm doing wrong?

Your problem is on your SubCategories model.You have to fix that as shown below.
Note : use public virtual Categories Categories { get; set; } on it.Hence you didn't do that,EF doesn't know how to fetch the related entities (or navigational property) from the db when you use Include or Lazy loading.And also you need to change CategoryID as CategoriesID.B'cos your model's name is Categories.
public class SubCategories
{
public int ID { get; set; }
public string Name { get; set; }
[ForeignKey("CategoriesID")]
public virtual Categories Categories{ get; set; }//you have to do this
public int CategoriesID { get; set; }
public string LinkToProducts { get; set; }
}

Try this:
public class SubCategories
{
public int ID { get; set; }
public string Name { get; set; }
[ForeignKey("CategoryID")]
public Categories Category { get; set; }
public int CategoryID { get; set; }
public string LinkToProducts { get; set; }
}

You are missing the navigation property in SubCategories class:
public virtual Categories Categories { get; set; }

Related

EF Core Many to Many Querying to join 3 tables

I have the following Code:
public class ProductTbl
{
public override int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class Manufacturer_LKP
{
public override int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class ProductManufacturer
{
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}
public class SupplierTbl
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
}
public class ProductSuppliertbl
{
public int Id { get; set; }
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public SuppilerTbl Supplier { get; set; }
public int SupplierID { get; set; }
}
*I need to write Linq query to join all 3 tables (Product,Manufacture,ProductManufacturer) to get ProductName and ManufatureName together in one DB trip
*When I do the following I missed the Manufacture object (Manufacture=Null)
DbSet<ProductTbl>()
.Where(a => a.Id == 5)
.AsNoTracking()
.Include(a => a.ProductType)
.Include(a => a.ProductManufacturer)
Above Linq Just joint Product table with ProductManufacture Table So I cannot Get "ManufactureName"
So Is there is any way to join the 3 tables to get ManufactureName beside the ProductName in one DB trip?
Projection is your friend when trying to load related data. The issue with many-to-many is that you are saying a product has many manufacturers, while at the same time it has many suppliers
The Product would need a reference to the ProductSuppliers for that product to easily manage the many suppliers requirement.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.ProductManufacturers.Select(pm => pm.Manufacturer.ManufacturerName).ToList(),
SupplierNames = x.ProductSuppliers.Select(ps => ps.Supplier.SupplierName).ToList()
}).ToList();
This gives you a list of products, with each product's associated manufacturer names and supplier names. With that data you can format output how you see fit.
If you want the entities themselves, then the missing bit is ThenInclude:
var products = context.Products
.Include(p => p.ProductManufacturers)
.ThenInclude(pm => pm.Manufacturer)
.Include(p => p.ProductSuppliers)
.ThenInclude(ps => ps.Supplier)
.AsNoTracking()
.ToList();
This would load the entire entity graph.
If you don't want or cannot put a ProductSuppliers collection in product then you can build the query entirely from the ProductSupplier, but it's a bit messier.
If you are using EF Core 5 and your joining entities (ProductManufacturer/ProductSupplier) are just simply the FK references to their respective entities, then you can do away with the joining entity and let EF manage it behind the scenes. Product would just contain a collection of Manufacturers and a collection of Suppliers. These can be configured still with a HasMany..WithMany, but makes queries a lot cleaner to look at without the intermediate entities.
I.e.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.Manufacturers.Select(m => m.ManufacturerName).ToList(),
SupplierNames = x.Suppliers.Select(s => ps.SupplierName).ToList()
}).ToList();
and
var products = context.Products
.Include(p => p.Manufacturers)
.Include(p => p.Suppliers)
.AsNoTracking()
.ToList();
... respectively. Intermediate joining entities are only needed if there are additional properties you want to access in the joining entity. (I.e. CreatedBy/At, etc.)
try this
var list = context.ProductManufactures
.Select(i => new
{
ProductName = i.Product.ProductName,
ManufacturerName = i.Manufacturer.ManufacturerName,
SupplierNames = i.Product.ProductSuppliers.Select(s => s.SupplierName).ToList()
}).ToList();
or you can try this too
var productData = context.Products
.Select(i => new
{
ProductName= i.ProductName,
ManufacturerNames = i.ProductManufacturers.Select(m => m.Manufacturer.ManufacturerName),
SupplierNames = i.ProductSuppliers.Select(s => s.Supplier.SupSupplierName)
}).ToList();
but before this you have to fix some navigation properties
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturers { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class Supplier
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class ProductSupplier
{
public int Id { get; set; }
public Product Product { get; set; }
public int ProductID { get; set; }
public Supplier Supplier { get; set; }
public int SupplierID { get; set; }
}
public class Manufacturer_LKP
{
public int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; }
}
public class ProductManufacturer
{
public Product Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}

LINQ statement issue for dynamic view component menu

I have three level (Category- Subcategory - Nestedcategory) dropdown navigation menu on my website for which data must come dynamically from database. My main problem in generation of InvokeAsync() method to make it work. I can write two levels which work fine as I checked, but confused in defining Nestedcategories - need to get it from subcategories which derived from categories.
Here is my Controller
public class MenuViewComponent: ViewComponent
{
private readonly SamirDbContext _samirDbContext;
public MenuViewComponent(SamirDbContext samirDbContext)
{
_samirDbContext = samirDbContext;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var menu = await _samirDbContext.Categories.Include(x => x.Subcategories).ThenInclude(y => y.NestedCategories).
Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
**NestedCategories = ...**
}).ToListAsync();
return View(menu);
}
}
Here are models:
public class Category
{
public Category()
{
Subcategories = new HashSet<Subcategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Subcategory> Subcategories { get; set; }
}
public class Subcategory
{
public Subcategory()
{
Posts = new HashSet<Post>();
NestedCategories = new HashSet<NestedCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public ICollection<Post> Posts { get; set; }
public ICollection<NestedCategory> NestedCategories { get; set; }
}
public class NestedCategory
{
public NestedCategory()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
public Subcategory Subcategory { get; set; }
public int SubcategoryId { get; set; }
}
Menu ViewModel
public class MenusModel
{
public int Id { get; set; }
public Category Category { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Subcategory> Subcategories { get; set; }
public Subcategory Subcategory { get; set; }
public IEnumerable<NestedCategory> NestedCategories { get; set; }
public NestedCategory NestedCategory { get; set; }
}
Please, help in completion InvokeAsyinc() method in order to get work for 3 level menu.
You can use SelectMany() method, change the linq like below:
var menu = await _samirDbContext.Categories
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
NestedCategories = x.Subcategories.SelectMany(s => s.NestedCategories).ToList()
}).ToListAsync();
Looking at your models for Category, Subcategory, and NestedCategory, I'm asking myself why you actually could need to have a separate output property (**NestedCategories = ...**) in your final Select statement.
Let's think this way if the NestedCategory is defined inside the Subcategory collection, then every Subcategory element should have its own list of NestedCategory-ies, which will be available when you will check some Subcategory from the dropdown.
So, my advice here is to leave the result as follows:
var menu = await _samirDbContext.Categories
.Include(x => x.Subcategories)
.ThenInclude(y => y.NestedCategories)
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories
.Select(sb => new SubcategoryDTO
{
sb.Id,
sb.Name,
...
NestedCategories = sb.NestedCategories
.Select(nst => new NestedCategoriesDTO
{
nst.Id,
nst.Name,
...
})
}),
}).ToListAsync();
Then you can use the above model in your UI.
Hope this will help ))

Join 3 One to Many Tables in Entity Framework

i have 2 tables that each one has a one-to-many relation to the table between and the table between has ids of 2 other tables
dbo.Posts dbo.Posts_Categories dbo.Categories
-ID -ID -ID
-Title -PostID -Name
-CategoryID
result i expect is :
Title = post1 Categories = web,mobile,desktop
Title = post2 Categories = app,game
...
i know how to query this in sql using Stuff function and For Xml Path but i have no idea how do i do this in entity framework!
any suggestion or book for how to do works in this way might help!
Edit: EF classes added:
public class Post : ReportingBase {
public Post() { }
[Required, MaxLength(500)]
public string Title { get; set; }
[Required, MaxLength(500)]
public string Address { get; set; }
[Required]
public string Body { get; set; }
[Required, MaxLength(500)]
public string Tags { get; set; }
[Required]
public int Visit { get; set; }
public virtual ICollection<Post_Category> Posts_Categories { get; set; }
public virtual ICollection<Post_AttachedFile> Posts_AttachedFiles { get; set; }
[ForeignKey("Image")]
public virtual int? ImageID { get; set; }
public virtual Image Image { get; set; }
}
public class Post_Category {
public Post_Category() { }
[Key, Column(Order = 0)]
public int PostID { get; set; }
[Key, Column(Order = 1)]
public int CategoryID { get; set; }
public virtual Post Post { get; set; }
public virtual Category Category { get; set; }
}
public class Category : EntityBase {
public Category() { }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, MaxLength(150)]
public string Address { get; set; }
public int? ParentID { get; set; }
public virtual ICollection<Post_Category> Posts_Categories { get; set; }
}
thank you in advance
Edit : According to #IvanStoev answer i did following :
List<P> p = context.Posts.Select(post => new {
Title = post.Title,
Categories = post.Posts_Categories.Select(pc => pc.Category.Name).ToList()
}).ToList();
and created a class called P :
public class P {
public string Title { get; set; }
public List<string> Categories { get; set; }
}
but it doesn't work correctly and the problem is how to return the result.
In EF it's even easier than in SQL thanks to the concept of so called navigation properties. All you need to know is a basic LINQ query syntax and just follow them (navigate) to get the data needed. For instance:
var result = db.Posts
.Select(post => new
{
Title = post.Title,
Categories = post.Posts_Categories
.Select(pc => pc.Category.Name)
.ToList()
})
.ToList();
The result is a list of anonymous type having string Title property and List<string> Categories property containing the related category names.
You can use Linqpad (software) to get familiarize with the Linq query it builds lambda expression for you by connecting to the database and provides output too to cross verify.
The below one is the lambda expression for joining the tables you have mentioned.
p - Post
pc - post_categories
c - categories
Code:
Posts.Join(Post_Categories, p => p.ID, pc => pc.ID, ( p, pc) => new { p = p, pc = pc})
.Join(Categories, pcc => pcc.pc.CategoryID, c => c.ID, ( pcc, c) => new { pcc = pcc, c = c})
.Select(p.Title)
.Select(c.Name)
You should be using .Include() for any join in EF Core.
I've come up with this simple example: one person can have many dogs.
public class Person
{
public Guid Id { get; set; }
public ICollection<Dog> Dogs { get; set; } // One Person can have many Dogs
}
public class Dogs
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
}
Generate the migrations after creating the models. Not going over how to do that in this answer.
Here's how you use .Include() to join upon the two different tables:
public class PersonRepository : RepositoryBase
{
public IEnumerable<Person> FetchPeopleWithManyDogs()
{
return DatabaseContext.Person
.Include(x => x.Dogs)
.Where(x => x.Dogs.Count() > 1).ToList();
}
}

How to get specific columns with one to many relationship

I have 3 tables , one to many relationship.
I need to get only specific columns with SelectMany method.
I need to get only Categories.CategoryName and Comments.CommentDate of the selected News object.
Here is my code
News news = db.News.Include(w => w.Categories)
.Include(w => w.Comments).SingleOrDefault(n => n.NewsId == Id);
Here are my Entities:
News Entity:
public partial class News
{
public News()
{
this.Categories = new HashSet<Category>();
this.Comments = new HashSet<Comment>();
}
public int NewsId { get; set; }
public string NewsTitle { get; set; }
public string NewsBody { get; set; }
public System.DateTime NewsDate { get; set; }
public string NewsImagePath { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
Category Entity:
public partial class Category
{
public Category()
{
this.News = new HashSet<News>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<News> News { get; set; }
}
Comment Entity:
public partial class Comment
{
public Comment()
{
this.News = new HashSet<News>();
}
public int CommentId { get; set; }
public string CommentBody { get; set; }
public Nullable<System.DateTime> CommentDate { get; set; }
public virtual ICollection<News> News { get; set; }
}
This LINQ query should take care of it:
var query =
from news in db.News
where news.Id == Id
let categoryNames =
from category in news.Categories
select category.Name
let commentDates =
from comment in news.Comments
select comment.CommentDate
select new {
CategoryNames = categoryNames.ToList(),
CommentDates = commentDates.ToList()
};
That query is not using SelectMany, but that wouldn't help you, since then you wouldn't be able to group your categories and comments by news items. Since categories and comments are not directly connected, you'd need two SelectManys and then you'd need to cross join the results. That would obviously not be what you want.
Maybe try using the following?
var categoryNames = news.Categories.Select(c=>c.CategoryName);
var commentDates = news.Comments.Select(c=>c.CommentDate);
Note that SelectMany is used to flatten lists.For example, lets say you have collection of news matching certain search criteria, and then you use SelectMany to collect all the Categories/Comments of these news set, in a flat list.

EF7 Add item to table with many to many

I have 2 tables with many to many relations, so I created 3rd table as join table, since EF7 will not create it automatically.
class Product
{
public int Id { get; set; }
public ICollection<Categorization> Categorizations { get; set; }
}
class Categorization
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
class Category
{
public int Id { get; set; }
public ICollection<Categorization> Categorizations { get; set; }
}
So if I want to insert/seed new records in to Products, I'm doing like this:
db.Products.AddRange(products); //where products = List<Product>
db.Categories.AddRange(categories);
db.SaveChanges();
db.Categories.ToList() //not using where class since tables are empty initially
.ForEach(c => db.Products.ToList()
.ForEach(p => db.Categorizations
.Add(new Categorization() { CategoryId = c.Id, ProductsId = p.Id })));
db.SaveChanges();
Is there any effective and simple way to do the same? Thank you.

Categories

Resources