EF Core many-to-many fetch query - asp.net-core-mvc

I have the following many-to-many relation modelled
public class Profile
{
ICollection<Category> Categories { get; set;}
// One-To-Many
ICollection<Platform> Platforms { get; set; }
}
public class Category
{
ICollection<Profile> Profiles { get; set; }
}
public class ProfileCategory
{
public int ProfileId { get; set; }
public Profile Profile { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set;}
}
I'm using ASP.NET Core MVC and have a filter view model where a filter on some attributes on profile name and it works.
Trying to filter based on category proved much harder to implement (at least the solution isn't obvious to me :)
From the web the user can select zero, one or many categories to filter on so basically what is sent to my controller is a list of category ids.
IQueryable<Profile> query = _context.Profiles.Include(p => p.Categories).Include(p => p.Platforms);
if(string.IsNullOrEmpty(search.Name))
{
query = query.Where(p => p.Name.IndexOf(search.Name StringComparison.OrdinalIgnoreCase) > 0);
}
if(search.Categories?.Any() != null)
{
query = query.SelectMany(p => p.ProfileCategories)
.Join(search.Categories, pc => pc.CategoryId, cId => cId, (pc,_) => pc.Profile);
}
From this point the Profile object is different and other navigational properties such as Platforms is null hence breaking other parts.
How can I perform the join while retaining the original instance of Profile object. I first thought that they would be the same, but I was wrong.

Currently, EF Core JOINs are not perfect, and I recommend make two queries:
1) Select list of ProfileId (based on category list):
var profileIds = await _context.ProfileCategory
.Where(x => categoryIds.Contains(x.CategoryId)) // filtering goes here
.Select(x => x.ProfileId)
.Distinct()
.ToListAsync();
2) Select required data based on known IDs:
var result = await _context.Profiles
.Include(p => p.Categories).Include(p => p.Platforms)
.Where(x => profileIds.Contains(x.ProfileId))
.ToListAsync();
Yes, this is two queries instead one, but two simple queries, easily optimized using indexes.

Related

Pass multiple parameters in web API GET method

I've the following piece of code to return a list of products based on category.
public IEnumerable<Product> GetProductsByCategoryId(string category_id)
{
return repository.GetAll().Where(
p => string.Equals(p.Category, category_id, StringComparison.OrdinalIgnoreCase));
}
I want to pass two parameters in the method, category and Brand.
public IEnumerable<Product> GetProductsByCategoryBrand(string category, string Brand)
{
}
What would the return method look like with category and Brand?
public IEnumerable<Product> GetProductsByCategoryBrand(string category, string Brand)
{
return repistory.GetAll().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)
&& string.Equals(p.Brand, brand, StringComparison.OrdinalIgnoreCase));
}
You just chain your conditions to the Where() using the && operator. If you want to match either brand or category, use ||

NHibernate Hitting database once for each record in query

I'm measuring database calls on a slow site using NHibernate profiler, and have noticed immediately that the following is causing a Select N+1 issue.
I've never used NHibernate so was hoping someone could help me out?
public virtual IQueryable<Employee> Employees()
{
return Session.Query<Employee>();
}
public IList<Employee> GetEmployeesByClientId(int clientId)
{
return Employees()
.Where(e => e.ClientId == clientId && e.Deleted == false)
.ToList();
}
At the point of calling ToList() a select statement is ran for every related record of EmployeeDetail, and I'm not sure why.
public virtual EmployeeDetail EmployeeDetail { get; set; }
You can use Fetch method to force a join sql statement to fill the property, for sample:
return Employees()
.Fetch(x => x.EmployeeDetail)
.Where(e => e.ClientId == clientId && e.Deleted == false)
.ToList();

Aggregate with OrderBy and ThenBy

I have a list which elements follows a sequence. I want use aggregate with OrderBy and ThenBy
List<string> list = new List<string> {"codCustomer", "customerName", "address1", "address2"};
list = list
.OrderBy(o => o.codCustomer)
.ThenBy(o => o.customerName)
.ThenBy(o => o.address1)
.ThenBy(o => o.addres2)
This list contain many elements and I want use aggregate with OrderBy and ThenBy, but I don't know how do it.
The matter is that this list is passed as a parameter and contains the fields to sort.
You should define a class called Customer with the properties Code, Name, Address1 and Address2 and create a list of customer objects, not a list of strings. Then you can order the list of customers the way you described.
class Customer
{
public string Code { get; set; }
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
}
...
// at some point
List<Customer> customers = new List<Customer>();
customers.Add(new Customer()
{
Code = "cod1",
Name = "nam1",
Address1 = "ad1",
Address2 = "ad2"
};
// ... more customers
// you order customers the way you need
List<Customer> orderedCustomers = customers.OrderBy(p => p.Code).ThenBy(p => p.Name).ToList(); // you can continue ordering by all the properties if you want.

Populate Dropdown List with DB table item

I have a table with about 3 items. I wish to populate the Dropdown List using that table. But i have a condition that i want to show only 2 items in the Dropdown List. How can it be done?
You could use a view model:
public class MyViewModel
{
public string SelectedItemId { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
and then take the first 2 items (obviously if your requirements is to take some other 2 items based on some condition you could chain with the .Where() extension method in order to filter first before calling .Take()):
public ActionResult Index()
{
var model = new MyViewModel();
model.Items = db.Items.Take(2).ToList().Select(x => new SelectListItem
{
Value = x.Id,
Text = x.SomeText
});
return View(model);
}
The view is standard stuff, simply call to the DropDownListFor helper:
#model MyViewModel
#Html.DropDownListFor(x => x.SelectedItemId, Model.Items)

Joining 2 table with LINQ to Entities

I have two tables, for Users and Songs. I need to JOIN them both on the UserID column and return a list to the view. I tried writing an anonymous type but I'm getting an error about:
'Only parameterless constructors and initializers are supported in LINQ to Entities.'
How can I JOIN and return to list?
public class User
{
[Key]
public virtual Guid UserId { get; set; }
[Required]
public virtual String Username { get; set; }
[Required]
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
}
public class Song
{
public int SongId { get; set; }
public virtual Guid UserId { get; set; }
public string SongTitle { get; set; }
}
I tried implementing a method like this one here:
What is the return type of my linq query?
The query I ended up with was:
var query = from u in db.Users
join s in db.Songs
on u.UserId equals s.UserId
select new HomeSongList(s.SongId, s.UserId, u.Username);
return View(query.ToList());
You can't use parameterized constructors but you can use initializers, if you change your select to the following it should work.
select new HomeSongList{SongId=s.SongId, UserId=s.UserId, Username=u.Username};
Note that this will require that HomeSongList has a parameterless constructor and writable properties.
Well the error message is rather clear, no ?
You try to create a new HomeSongList in an linq2entity query (which will be translated in sql : not possible)
With the "User property" correction, you should make
var query = db.Songs.Select(s => new{
songId = s.SongId,
userId = s.User.UserId,
userName = s.User.Username})
.ToList()
.Select(x => new HomeSongList(x.songId, x.userId, x.userName);
or with your actual code
var query = (from u in db.Users
join s in db.Songs
on u.UserId equals s.UserId
select new {
songId = s.SongId,
userId = s.UserId,
userName = u.Username
})//new anonymous object is possible in linq2entities
.ToList()
.Select(x => new HomeSongList(x.songId, x.userId, x.userName);

Resources