Where list contains any in List - c#

I am just sending this data to reporting Engine(SSRS) in Asp.net MVC5.
Everything is fine, but this query is taking a lot of time since I have to loop through ListProducts (I guess ListProducts is the size of the database matches).
I am just looking for a way to optimize this query.
I have tried any and contains (as seen below), but they do not seem to work in single table.
context.Products.Where(w => w.ProductDetail.Any(a => a.startDate >= startDate
&& a.endDate <= endDate))
I got this from here
2)I tried this as well
context.Products.Where(w => ListProducts.Any(x =>w.Contains(x)))
but this also does not work and generates a compile time error that
System.Guid does not contains definition of 'Contains'
Is there any other way, or i am doing it the only correct way?
foreach (var item in ListProducts)
{
List.AddRange(_context.Products.Where(w => w.ProductId== item).Select(q => new ProductVM
{
Name = q.Name,
Quantity = q.Quantity,
}).ToList().Select(item=> new ProductVM
{
Name = item.Name,
Quantity = item.Quantity,
}).ToList());
}
public class Product
{
public Nullable<System.Guid> ProductId { get; set; }
public string Name { get; set;}
public decimal Quantity { get; set; }
}

Ok, can you do:
var thelist = _context.Products.Where(n => ListProducts.Contains(n.ProductId)).Select(n => new ProductVM
{
Name = n.Name,
Quantity = n.Quantity
}).ToList();

Related

Unable to get product names based on condition

I have two model classes
public class GetProductNameRequest
{
public DateTime ExpiryDate { get; set; }
public bool IsActive { get; set; }
}
public class GetProductNameResponse
{
public List<string> ProductName { get; set; }
}
linq query:
public async Task<List<ProductSKU>> GetProductNames(GetProductNameRequest request)
{
var res = DbSet.Include(t => t.Product).Where(t => t.EndDate >= request.ExpiryDate && t.IsActive == request.IsActive).GroupBy(x => x.Product);
Contracts.Models.ProductNameResponse product = new Contracts.Models.ProductNameResponse();
foreach (var item in res)
{
product.ProductName = item.Key.ProductName;
}
}
So i'm unble get list of Product Names based on Id's plz let me know the solution.
productsku table:
SkuId ProductId Sku MRP CreatedOn UpdatedOn StartDate EndDate IsActive RowVersion
You have a number of problems in your code. Two most obvious reasons why you don't get anything in your product variable is that it is initialized inside the loop, and nothing gets added to it. The code should be something like
Contracts.Models.ProductNameResponse product = new Contracts.Models.ProductNameResponse();
foreach (var item in res)
{
product.ProductName.Add(item.Key.ProductName);
}
I also think your LINQ statement will still throw an error about one cursor not close while another one is open. Search for IQueryable vs IEnumerable issue. But you don't list that as a problem; so maybe in your data source it is fine.

EFCore SqlException for SelectListItem projection using same property for Value and Text

I created a SelectListHelper to keep all of my dropdown data population in a central location. It projects rows in my database into collections of IEnumerable<SelectListItem> that can be consumed by my views. Here is a portion of the class:
public class SelectListHelper
{
private AppDbContext _db;
public SelectListHelper(AppDbContext db)
{
_db = db;
}
public IEnumerable<SelectListItem> GetCountries(bool showAll = false)
{
return _db.Set<Country>().Where(x => showAll || x.Enabled == true).OrderBy(x => x.Name).Select(x => new SelectListItem { Value = x.Abbreviation, Text = x.Name }).ToList();
}
public IEnumerable<SelectListItem> GetGoogleCategories(bool showAll = false)
{
return _db.Set<GoogleCategory>().Where(x => showAll || x.Enabled == true).OrderBy(x => x.Name).Select(x => new SelectListItem { Value = x.Name, Text = x.Name }).ToList();
}
}
GetCountries, and all other functions omitted for brevity, work just fine. They use distinct columns projected onto the Value and Text properties of the SelectListItem.
GetGoogleCategories projects the Name column onto both the Value and Text properties of the SelectListItem. This produces the following SqlException:
An exception of type 'System.Data.SqlClient.SqlException' occurred in
Microsoft.EntityFrameworkCore.dll but was not handled in user code
Invalid column name 'Value'.
When I looked at the SQL being generated by the GetCountries function, it looked like I expected:
SELECT [x].[Abbreviation] AS [Value], [x].[Name] AS [Text]
FROM [Countries] AS [x]
WHERE [x].[Enabled] = 1
ORDER BY [Text]
However, the SQL generated by the GetGoogleCategories function did not look like I expected:
SELECT [x].[Name] AS [Text]
FROM [GoogleCategories] AS [x]
WHERE [x].Enabled] = 1
ORDER BY [Value]
I'm using EfCore 2.1.0 in Visual Studio 2017 (15.7.3). Any ideas what might be going on here? I can work around this by returning an array of Names and manually building a list of SelectListItems, but this has me worried about what other things in my Data Access Layer might be working improperly.
public class GoogleCategory
{
[Key]
public int Id { get; set; }
[Required, StringLength(250)]
public string Name { get; set; }
public bool Enabled { get; set; } = true;
}
public class Country
{
[Key]
public int Id { get; set; }
[Required, StringLength(250)]
public string Name { get; set; }
[Required, StringLength(2)]
public string Abbreviation { get; set; }
public bool Enabled { get; set; } = true;
}
It's a 2.1 regression bug tracked by #12180: Invalid column name: orderby uses a column alias that does not exist.
The workaround for your particular case is to use the SelectListItem constructor with parameters (a feature introduced in 2.1):
return _db.Set<GoogleCategory>()
.Where(x => showAll || x.Enabled == true)
.OrderBy(x => x.Name)
.Select(x => new SelectListItem(x.Name, x.Name) })
.ToList();
but of course the concerns remain.

Nested foreach loops to LINQ

Is it possible to rewrite this code to LINQ? I'm new to linq and it seems difficult to understand.
foreach (Employee employee in EmployeeList)
{
Earnings earnings = new Earnings(employee.Name, employee.LastName, employee.Bank, employee.Account);
if (!EarningsList.Contains(earnings))
{
EarningsList.Add(earnings);
}
foreach (DaysData item in ProductList)
{
foreach (Product product in item.Products)
{
if (product.EmployeeName == employee.Name && product.EmployeeLastName == employee.LastName)
{
double money = product.Count * product.Price;
earnings.AddMoney(money);
}
}
}
}
The first part isn't so easy to convert because of the conditional EarningsList.Add()
But You can rewrite the last 2 rather easily.
Assuming that AddMoney() does just what it says, you can use Sum(). Otherwise, omit the Sum() and run a separate foreach on the list of amounts. That would make it a lot less Linq.
var amount = ProductList
.SelectMany(item => item.Products)
.Where(product => product.EmployeeName == employee.Name && product.EmployeeLastName == employee.LastName)
.Sum(product => product.Count * product.Price)
;
earnings.AddMoney(amount);
Not knowing exactly what the .AddMoney method does you could do this:
var query =
from employee in EmployeeList
let earnings = new Earnings(employee.Name, employee.LastName, employee.Bank, employee.Account)
from item in ProductList
from product in item.Products
where product.EmployeeName == employee.Name
where product.EmployeeLastName == employee.LastName
group product.Count * product.Price by earnings;
List<Earnings> EarningsList =
query
.Aggregate(new List<Earnings>(), (a, x) =>
{
a.Add(x.Key);
foreach (var y in x)
{
x.Key.AddMoney(y);
}
return a;
});
If .AddMoney simply adds the money arithmetically then you could do this:
List<Earnings> EarningsList =
query
.Aggregate(new List<Earnings>(), (a, x) =>
{
a.Add(x.Key);
x.Key.AddMoney(x.Sum());
return a;
});
Just a small note. You're using double to represent money. It's best to use decimal as this will help prevent rounding errors in your calculations.
I think this is what you want if you just used LINQ(no foreach). This should be compatible with IQueryable as well and you really don't want to do foreach on IQueryable.
var newEarnings = from employee in EmployeeList
select new Earnings
{
Name = employee.Name,
LastName = employee.LastName,
Bank = employee.Bank,
Account = employee.Account,
Money = (from daysData in ProductList
from product in daysData.Products
where employee.Name == product.EmployeeName && employee.LastName == product.EmployeeLastName
select product).Sum(p => p.Count * p.Price)
};
EarningsList = EarningsList.Union(newEarnings).ToList();
Now in regards to normalization. My guess is that you made you POCO models like this in order to show it in some sort of a grid. You really should not let your UI dictate what you data models look like. There can be others reasons to do this but they are related to performance and I don't think you need to worry about this just jet. So here is my advice on how to change this.
Add Id property to all the classes. This is always a good idea no
matter what you are doing. This can be a random string or an auto
increment, just to have a unique value so you can play with this
object.
Add reference properties in you classes. Don't copy the values from
Employee to Product and Earnings. Just add a Property of type
Employee and/or add the EmployeeId property
So your POCO should look something like this
public class Employee
{
//It can be a Guid, string or what ever. I am not nesseserly recomending using Guids and you should look into this a bit more
public Guid Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Bank { get; set; }
public string Account { get; set; }
}
public class DaysData
{
public Guid Id { get; set; }
public IEnumerable<Product> Products { get; set; }
}
public class Product
{
public Guid Id { get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
public double Count { get; set; }
public double Price { get; set; }
}
public class Earnings
{
public Guid Id { get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
public double Money { get; set; }
}
And the query
var newEarnings = from employee in EmployeeList
select new Earnings
{
Id = Guid.NewGuid(),
EmployeeId = employee.Id,
Employee = employee,
Money = (from daysData in ProductList
from product in daysData.Products
where employee.Id == product.EmployeeId
select product).Sum(p => p.Count * p.Price)
};
Once you try to implement data persistence this will help you a lot no matter what you use EF, Mongo, Elsatic, AzureTables or anything else even a simple saving to files.

LINQ select all items of all subcollections that contain a string

I'm using jqueryui autocomplete to assist user in an item selection. I'm having trouble selecting the correct items from the objects' subcollections.
Object structure (simplified) is
public class TargetType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubCategory> SubCategories { get; set; }
public TargetType()
{
SubCategories = new HashSet<SubCategory>();
}
}
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTargetType> SubTargetTypes { get; set; }
public SubCategory()
{
SubTargetTypes = new HashSet<SubTargetType>();
}
}
Currently I'm doing this with nested foreach loops, but is there a better way?
Current code:
List<SubTargetResponse> result = new List<SubTargetResponse>();
foreach (SubCategory sc in myTargetType.SubCategories)
{
foreach (SubTargetType stt in sc.SubTargetTypes)
{
if (stt.Name.ToLower().Contains(type.ToLower()))
{
result.Add(new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
}
}
}
You can do using Linq like this
var result = myTargetType.SubCategories
.SelectMany(sc => sc.SubTargetTypes)
.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))
.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
The above query doesn't work. The following should work, but I'd not recommend that as that'd not be faster or more readable.
var result = myTargetType.SubCategories
.Select(sc => new Tuple<int, IEnumerable<SubTargetType>>
(sc.Id,
sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))))
.SelectMany(tpl => tpl.Item2.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = tpl.Item1,
Name = stt.Name }));
Actually there are 2 different questions.
LINQ select all items of all subcollections that contain a string
Solutions:
(A) LINQ syntax:
var result =
(from sc in myTargetType.SubCategories
from stt in sc.SubTargetTypes.Where(t => t.Name.ToLower().Contains(type.ToLower()))
select new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
(B) Method syntax:
var result =
myTargetType.SubCategories.SelectMany(
sc => sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower())),
(sc, stt) => new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
Currently I'm doing this with nested foreach loops, but is there a better way?
Well, it depends of what do you mean by "better". Compare your code with LINQ solutions and answer the question. I personally do not see LINQ being better in this case (except no curly braces and different indentation, but a lot of a hidden garbage), and what to say about the second LINQ version in this answer - if that's "better" than your code, I don't know where are we going.

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

Categories