loading related entities - c#

We are using EF .NET core for our architecture and want to do a basic query. So all we are after is using both LINQ & EF with lazy loading switched off to select the parent, in this case stick item and some of the fields in the child objects. Then return them back into our strongly typed item.
Something like this.
var qry = _context.Set<stock>()
.Include(p => p.stockitem)
.Where(q => q.customer == accountNo)
.Select(r => new stock() {
customer = r.customer,
r.stockitem.select(s => new {id, s.id, s.name }})
.ToList();
So is it possible to do this? basically get hold of say just a couple of columns from our child object. Then have everything returned in the strongly typed object.

First create a model in which the selected data will be stored (This model is just an example):
public class MyNewCustomer
{
public string CustomerAccount { get; set; }
public Dictionary<int, string> Items { get; set; }
}
After that you can create a new object from your select:
var data = _context.Stocks
.Include(p => p.stockitem)
.Where(p => p.customer == accountNo)
.Select(p => new MyNewCustomer
{
CustomerAccount = p.customer,
Items = p.stockitem.ToDictionary(s => s.id, s => s.name)
}).ToList();
PS: I used Dictionary because you didn't provide the actual model. You can choose whatever kind of List<TObject> you want.

Related

Group by and count on list lambda c#

I have 2 tables in the database Department and SubDepartment. Each department can or cannot have multiple sub departments. I am fetching the data from both the tables combined in the form of following class.
This class have more properties as well from both the tables.
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public int SubDepartmentId { get; set; }
public string SubDepartmentName { get; set; }
}
For example data can be Department as Science which will have multiple Sub Departments as Physics, Chemistry. Also Department can be English which will have one sub department as English only.
Now from the data in the form of List of Departments collection fetched from the DB I have to fetch those departments which have multiple sub departments.
Below is the code:
List<Department> departments = new List<Department>();
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments .GroupBy(x => x.DepartmentId)
.Where(x => x.Count() > 1)
.Select(x => new { DepartmentId = x.Key }).ToList();
// Here getting the entire data/item for each dept and subdept
var finalDeptData = departments.Where(x => multidepartmentIds.Any(y => y.DepartmentId == x.DepartmentId)).ToList();
Is there a better way to implement this?
I have thought of one more way as well where in a single lambda statement after Count we can have an anonymous type with all the class properties but the problem is that I have to return this collection to the caller method and then in that case I have to return the anonymous collection as type object which will require changes in the caller method as well.
Any help??
Looking at your code, IMO you are trying to get a list of all sub-departments which their department has multiple sub-department. In this case, you can do this:
List<Department> departments = new List<Department>();
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments
.GroupBy(x => x.DepartmentId)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList();
There is no need to create an anonymous type to hold a single value. Since you are looking for matches in the collection of IDs, use a HashSet to hold them:
// Here fetching deptids which are having multi sub deptids
var multidepartmentIds = departments.GroupBy(d => d.DepartmentId)
.Where(dg => dg.Count() > 1)
.Select(dg => dg.Key)
.ToHashSet();
// Here getting the entire data/item for each dept and subdept
var finalDeptData = departments.Where(d => multidepartmentIds.Contains(d.DepartmentId)).ToList();
However, since all you really want is all the departments where there are multiple occurrences, you can do that in one statement:
// find all departments with multiple sub deptids and return them
var finalDeptData2 = departments.GroupBy(d => d.DepartmentId)
.Where(dg => dg.Count() > 1)
.SelectMany(dg => dg)
.ToList();
I like to have an extension method for this:
public static class IEnumerableExt {
public static IEnumerable<T> DuplicatesBy<T,TKey>(this IEnumerable<T> src, Func<T,TKey> keyFn, IEqualityComparer<TKey> comparer = null)
=> src.GroupBy(s => keyFn(s), comparer).Where(sg => sg.Count() > 1).SelectMany(sg => sg);
}
Which you can use like:
var findDeptData3 = departments.DuplicatesBy(d => d.DepartmentId).ToList();

How to filter data inside of the navigation property

Suppose that I have 2 tables "Form" and "Status". Form table contains some properties and the navigation property that refers to Status table.
// Form table model
public class Form
{
...Some properties,
public ICollection<Status> Statuses { get; set; }
}
// Status table model
public class Status
{
...Some properties,
public DateTime FormTimeStart { get; set; }
public DateTime FormTimeFinish { get; set; }
}
The problem is that, I want the entire data from Form table including Status, but I also want to filter the data from Status table if today is earlier than expired date or not.
Here is what I've tried, But it could not give any response.
var form = _context.Forms
.Include(s => s.Statuses.Where
(i => DateTime.Compare(DateTime.Now,i.FormTimeFinish) < 0))
.Include(t => t.FormTopics)
.ThenInclude(q => q.Questions)
.ToList();
There's no way to do this in a single query. You'll need to explicitly load the Statuses relationship, after the fact:
var form = await _context.Forms
.Include(t => t.FormTopics)
.ThenInclude(q => q.Questions)
.ToListAsync();
form.Statuses = await _context.Entry(form)
.Collection(x => x.Statuses).Query()
.Where(i => DateTime.Compare(DateTime.Now, i.FormTimeFinish) < 0))
.ToListAsync();
However, be very careful with this, as you only have a subset of the statuses. So if you do anything like attempt to save the form, you'll end up deleting anything not in that list (it will look to EF as if you removed all the other statuses from the collection). It's best for this reason to disable tracking, in the initial query so that it will be more obvious that something's not right if you accidentally forget and attempt to save:
var form = await _context.Forms
.Include(t => t.FormTopics)
.ThenInclude(q => q.Questions)
.AsNoTracking()
.ToListAsync();

Cannot implicit convert type List to IEnumerable while grouping with Linq

New to MVC and Linq.
I'm was able to display all records just fine but now I was trying to get a count of records by name and just select 2 fields:Name and Count
I thought I should create a new ViewModel, fill the Name,Count and send it to the view.
public ActionResult Index()
{
var load =
db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new {Name = g.Key, Count = g.Count()}).ToList();
var viewModel = new loadingViewModel
{
LoadingListCount = load
};
return View(viewModel);
}
The linq above works as expected.
ViewModel:
public class loadingViewModel
{
public IEnumerable<LoadingListCount> LoadingListCount { get; set; }
}
public class LoadingListCount
{
public string Name{ get; set; }
public int Count { get; set; }
}
However, I'm getting an error. Cannot implicitly convert type 'System.Collections.Generic.List<>' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)
I have trying converting the query to list and to IEnumerable but no luck. I've searched around other posts but I have not have luck with them.
You are getting the error because of two main things, first one is the Linq produces a collection of an anonymous type, and the attached .ToList() gives you the result as List of anonymous type objects. But the expected result would be of type IEnumerable<LoadingListCount> so here we need to do few changes, First of all, we have to create an object of type LoadingListCount now the Linq will give you the output as expected, But the attached .ToList() will convert them to List<LoadingListCount> to avoid that we have to remove them as well. Finally, the query will look like the following:
var load = db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new LoadingListCount {Name = g.Key, Count = g.Count()})
.Select(g => new {Name = g.Key, Count = g.Count()})
produces objects of anonymous type, and puts them into a list. Since you need IEnumerable<LoadingListCount>, not IEnumerable<SomeAnonymousType> create instances of LoadingListCount instead by specifying the type in the invocation of new operator:
.Select(g => new LoadingListCount {Name = g.Key, Count = g.Count()})
Your query is creating a collection of anonymous objects - you need to project your query into your model
IEnumerable<LoadingListCount> load =
db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new LoadingListCount { Name = g.Key, Count = g.Count() })
var viewModel = new loadingViewModel
{
LoadingListCount = load
};

How can I make a more efficient Entity Framework LINQ call than a nested loop

Is there a more efficent way to write this LINQ statement?
Feature uses the same model as Product (as they are almost identical).
var products = db.Products.Where(q => q.IsFeature == false).ToList();
foreach (Product pr in products)
{
var features = db.Products.Where(q => q.ParentID == pr.ID).ToList();
foreach(Product feature in features)
{
pr.Features.Add(feature);
}
}
And the model:
public class Product
{
//START
public Product()
{
Features = new HashSet<Product>();
}
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsFeature { get; set; }
public int ParentID { get; set; }// If IsFeature is true we need to know its parent
//Nav Props
public ICollection<Product> Features { get; set; }// Features can be represented with the same model as product as they are almost identical
}
This does what I need ie a List of Product (Products), each with its own List of Feature (Features) but are there more elegant/efficient alternatives?
What you need is a join:
var productFeatures =
db.Products.Where(q => !q.IsFeature)
.Join(db.Products, p => p.ID, f => f.ParentId, (p, f) => new { product: p, features: f.ToList() })
.ToList();
For each object in db.Products.Where(q => !q.IsFeature) this will find each of the db.Products that have a ParentId equal to its ID field.
The results will be projected into a List() of anonymous types with the fields product (Product) and features (List<Product>)
If this operation has a complexity which rises to the point where the generated Linq's SQL becomes a hindrance or this business logic is done in multiple places; consider placing the logic in a stored procedure.
Once in the database simply map the results in EF which later can easily be consumed by the code. Hence the complexity is moved off of the client and directly on the database.
Steps (Database First)
Create Stored procedure.
Update the model from the database and choose the stored procedure.
Verify the resultant mappings in EF's Model Browser.
Consume the named stored procedure off of the data context.
I discuss the ins-and-outs of mapping stored procedures on my blog article Entity Framework Stored Procedure Instructions.
You're almost there you can use Addrange method of List collection:
var products = db.Products.Where(q => !q.IsFeature).ToList();
foreach(Product pr in db.Products)
if(!pr.IsFeature) pr.Features.AddRange(db.Products.Where(q => q.ParentID == pr.ID));
P.S. other idea is to extract features sort them, convert to array and use for loop :
var features = db.Products.Where(q => q.IsFeature).OrderBy(f => f.ParentID).ToArray();
p = db.Products.First(p => p.ID == features[0].ParentID);
for(int i = 0; i < features.Length; i++ )
{
if(i > 0 && features[i - 1].ParentID != features[i].ParentID)
p = db.Products.First(p => p.ID == features[i].ParentID);
p.Features.Add(features[i]);
}
You need to use GroupJoin for this. This is exactly what it's designed for.
var products = db.Products.Where(p => !p.IsFeature)
.GroupJoin(db.Products, p => p.ID, q => q.ParentID, (p, eq) => {
p.Features = eq.ToList();
return p;
})
.ToList();
This code is untested, but should work.

Entity Filter child without include

i'm a C# developer and i have a trouble with Entity Framework 5.
I have mapped my database with Entity using the default code generation strategy. In particolar there are three classes: menus, submenus and submenuitems.
The relationships about three classes are:
one menu -> to many submenus
one submenu -> to many submenuitems.
All classes have a boolean attribute called "Active".
Now, i want to filter all the Menus with the SubMenus active, and the SubMenus with the SubMenuItems active.
To get this i've tried this:
var tmp = _model.Menus.Where(m => m.Active)
.Select =>
new
{
Menu = x,
SubMenu = x.SubMenus.Where(sb => sb.Active)
.Select(y =>
new
{
SubMenu = y,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
})
})
.Select(x => x.Menu).ToList();
But didn't work.
Someone can help me?
Thank you for your help!
Hi have you see this post? Entity Framework: Querying child entities. there are some difference from your code, maybe this helps you.
UPDATE: Projection is when the result of a query is output to a different type than the one queried, it can be to an anonymous type, but could also be to a concrete type. And so using Data transfer object can be usefull to pass data between processes you can read a full explaination here Data Transfer objects
public class MenuDto
{
public int MenuId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<MenuDto> SubMenus { get; set; }
}
_model.Menus.Where(m => m.Active)
.Select(p => new MenuDto
{
MenuId = p.idField,
Name = p.NameField,
Url = p.UrlField,
SubMenus = p.SubMenus.Where(sb => sb.Active)
.Select(y => new MenuDto
{
MenuId = y.idField,
Name = y.NameField,
Url = y.UrlField,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
.Select(z => new MenuDto
{
MenuId = z.idField,
Name = z.NameField,
Url = z.UrlField
})
})
}).ToList();
I hope this can solve your problem
can you try this:
List<SubMenuItems> tmp = _model.menus.Where(a => a.active)
.SelectMany(b => b.SubMenus.Where(a => a.active)).ToList()
.SelectMany(c => c.SubMenuItems.Where(a => a.active)).ToList();
I hope it's helping.

Categories