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.
Related
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 ))
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();
}
}
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; }
I have the following requirements:
One rating can have zero or many RatingPictures
One Rating can have zero or many Comments
One Comment belongs to one User
This is what I have so far:
from rating in Ratings
where rating.LocationID == 1007
join ratingpicture in RatingPictures
on rating.ID equals ratingpicture.RatingID into j3
from ratingpicture in j3.DefaultIfEmpty()
join comment in Comments
on rating.ID equals comment.RatingID into j1
from comment in j1.DefaultIfEmpty()
join user in Users
on comment.UserID equals user.ID into j2
from user in j2.DefaultIfEmpty()
group new { ratingpicture, comment, user } by rating into g
select new { rating = g.Key, ratingpicture= g.Key, comment = g.ToList() }
If you model your entity classes like this:
public class Comment
{
public int CommentId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int UserId { get; set; }
}
public class Rating
{
public Rating()
{
RatingPictures = new HashSet<RatingPicture>();
Comments = new HashSet<Comment>();
}
public int RatingId { get; set; }
public virtual ICollection<RatingPicture> RatingPictures { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public int LocationID { get; set; }
}
public class RatingPicture
{
public int RatingPictureId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
}
Then your query would be as simple as this:
var query = context.Ratings.Where(r => r.LocationID == 1007)
.Include(r => r.RatingPictures)
.Include(r => r.Comments.Select(c => c.User));
var result = query.ToList();
Under the hood, this query will be translated into a join query. But this is the beauty of ORMs, we get to work at a more abstract level.
Take a look at this reference for more information about relationships and navigation properties in Entity Framework.
I have made simple model for example.
public class Publisher
{
public int Id { get; set; }
public string Title { get; set; }
public Address Location { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int LanguageId { get; set; }
public int? PublisherId { get; set; }
}
I need to get publishers with related books. I know how to do it using linq to entities. Is it possible to solve a problem using entity sql?
public class CatalogContext : DbContext {...}
public List<Publisher> GetByCity(string city)
{
var result = new List<Publisher>();
string queryString;
queryString = String.Format(#"SELECT VALUE row(a,b)
FROM CatalogContext.Publishers AS a
join CatalogContext.Books AS b on a.Id = b.PublisherId
WHERE a.Location.City = '{0}'", city);
var rows = ((IObjectContextAdapter)_context).ObjectContext.CreateQuery<DbDataRecord>(queryString).ToList();
return ???
}
Query returns required data but it's List<DbDataRecord> - list of pairs <publisher, book>. How to translate it to list of publishers with filled navigation property "Books"?
Is it possible to write query which directly returns List<Publisher>?
you can do the following:
var result = ObjectContext.Publishers.Include("Books").Include("Locations")
.Where(c => c.Location.City = "SOME_CITY").Select(c => c);
Include - basically joins the table.
Then you can drill down to books by doing the following:
var test = result[0].Books;
Why are you using direct sql command instead of Entity Framework code style?