LINQ EF where with list of class for All - c#

we have set.All(value) in linq, when i use list of long in set parameter everything is ok.
List<long> searchIds = new List<long>();
searchIds.Add(1);
using (ClearWhiteDBEntities cwContext = new ClearWhiteDBEntities())
{
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
join ads in cwContext.tblAds on adField.adId equals ads.id
group adField by adField.adId into adAdFields
where searchIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
select adAdFields.Key;
MessageBox.Show(adsWithRelevantadFields.Count().ToString());
}
but when i use list of class i get error:
var lstId = new ListIds[]
{
new ListIds { listId = 1 },
};
class ListIds
{
public long listId { get; set; }
}
using (ClearWhiteDBEntities cwContext = new ClearWhiteDBEntities())
{
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
join ads in cwContext.tblAds on adField.adId equals ads.id
group adField by adField.adId into adAdFields
where lstId.All(i => adAdFields.Select(co => co.listId).Contains(i.listId))
select adAdFields.Key;
MessageBox.Show(adsWithRelevantadFields.Count().ToString());
}
problem is in where line,
and error is:
unable to create a constant value of type 'ClearWhite.Handlers.ListIds. Only primitive types('such as Int32, String, and Guid') are supported in this context.

Error message is quite simple. Entity Framework is not able to produce correct SQL from your query.
You can either get List<long> before making a query:
var ids = lstId.Select(i => i.listId).ToList();
and then use it within the query:
where ids.All(i => adAdFields.Select(co => co.listId).Contains(i))
or try changing your query a little bit (but I'm not sure it will do the trick):
where lstId.Select(i => i.listId).All(i => adAdFields.Select(co => co.listId).Contains(i))

Related

MongoDB C# Aggregation - unwind -> groupBy

I have object Order with OrderItems.
I need grouped the data by field ProductId (in orderItems) and show sum for each product.
This solution works well:
var collection = database.GetCollection<Order>("Order");
var result = collection.Aggregate().Unwind(x=>x.OrderItems)
.Group(new BsonDocument
{
{"_id", "$OrderItems.ProductId"},
{"suma", new BsonDocument
{
{ "$sum" , "$OrderItems.UnitPriceExclTax"}
}
}
}).ToListAsync().Result;
But I don't want use pipeline, I need prepare full sample in c#.
And this solution doesn't work.
var collection = database.GetCollection<Order>("Order");
var result = collection.Aggregate()
.Unwind(x=>x.OrderItems)
.Group(i => i.ProductId, g => new { ProductId = g.Key, Count = g.Sum(i.UnitPriceExclTax) })
Thanks for any help,
I found solution,
I've prepared extra class:
[BsonIgnoreExtraElements]
public class UnwindedOrderItem
{
public OrderItem OrderItems { get; set; }
}
var agg = database.GetCollection<Order>("Order")
.Aggregate()
.Unwind<Order, UnwindedOrderItem>(x => x.OrderItems)
.Group(x=>x.OrderItems.ProductId, g => new
{
Id = g.Key,
Suma = g.Sum(x=>x.OrderItems.PriceExclTax)
})
.ToListAsync().Result;
It seems to me that you're trying to use linq syntax instead of mongodb. I don't know of any solution that would allow similar syntax to what you've described above in the aggregation-pipeline.

Select by ID from a list

I have a table of products and a table of categories, I can select by the ID of the Category like this:
var result = db.tblProducts.Where(p => p.tblCategories.Any(c => c.ID == 1));
However, I want to be able to select based on a list of Categories:
var catIDs = new List<int>() { 1,2,3 };
var results = db.tblProducts.Where(r => r.tblCategories.Any(t => catIDs.Contains(t.ID)));
I get the following error:
LINQ to Entities does not recognize the method 'Boolean Contains(Int32)' method, and this method cannot be translated into a store expression.
Presumably because I am using Contains to compare entities to local variables. Is there a way to do this?
Try create Expression from values. F.e.:
static Expression MakeOrExpression<T, P>(Expression<Func<T, P>> whatToCompare, IEnumerable<P> values)
{
Expression result = Expression.Constant(true);
foreach (var value in values)
{
var comparison = Expression.Equal(whatToCompare, Expression.Constant(value));
result = Expression.Or(result, comparison);
}
return result;
}
How to use:
var results = db.tblProducts.Where(r => r.tblCategories.Any(MakeOrExpression(t => t.ID, catIDs)));
The method MakeOrExpression will create an expression t.ID == 1 || t.ID == 2 || t.ID == 3 for list { 1, 2, 3 } dynamically, and then EF will translate it to SQL condition.
Maybe you can use this:
var catIDs = new List<int>() { 1,2,3 };
var results = db.tblCategories
.Where(t => catIDs.Contains(t.ID))
.SelectMany(t => t.tblProducts)
.Distinct();
Try this:
var query=from p in db.tblProducts
from c in p.tblCategories
where catIDs.Contains(c.ID)
select p;
If at least one of the categories of the product is in the catIDs list, then the product will be seleted.
Another option could be start by the categories (I'm guessing you have a many to many relationship between Product and Category and you have a collections of products in your Category entity):
var query=db.tblCategories.Where(c => catIDs.Contains(c.ID)).SelectMany(c=>c.tblProducts).Distinct();
Try this code :
var catIDs = new List<int>() { 1,2,3 };
var results = db.tblProducts.Where(r => catIDs.Any(c => c == r.tblCategories.Id));

Group By Query with Entity Framework

In my application I have Movements associated with a category.
I want a list of the most frequent category.
My objects are:
Category: catId, catName
Movement: Movid, movDate, movMount, catId
I think it would have to raise it with a "Group By" query (grouping by catId and getting those more)
(Im using Entity Framework 6 in c#)
From already thank you very much!
IMPORTANT: Entity Framework 7 (now renamed to Entity Framework Core 1.0) does not yet support GroupBy() for translation to GROUP BY in generated SQL. Any grouping logic will run on the client side, which could cause a lot of data to be loaded.
https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2
group the movements by category and select catid and count.
join this result with category to get the name and then descending sort the results on count.
var groupedCategories = context.Movements.GroupBy(m=>m.catId).Select(g=>new {CatId = g.Key, Count = g.Count()});
var frequentCategories = groupedCategories.Join(context.Categories, g => g.CatId, c => c.catId, (g,c) => new { catId = c.catId, catName = c.catName, count = g.Count }).OrderByDescending(r => r.Count);
foreach (var category in frequentCategories)
{
// category.catId, category.catName and category.Count
}
i hope this help:
var query = dbContext.Category.Select(u => new
{
Cat = u,
MovementCount = u.Movement.Count()
})
.ToList()
.OrderByDescending(u => u.MovementCount)
.Select(u => u.Cat)
.ToList();
I resolved the problem!
I used the proposal by "Raja" solution (Thanks a lot!).
This return a collection composed of "Category" and "Count". I Change it a bit to return a list of Categories.
var groupedCategories = model.Movement.GroupBy(m => m.catId).Select(
g => new {catId= g.Key, Count = g.Count() });
var freqCategories= groupedCategories.Join(model.Category,
g => g.catId,
c => c.catId,
(g, c) => new {category = c, count = g.Count}).OrderByDescending(ca => ca.count).Select(fc => fc.category).ToList ();
you just need to use navigation property on category simply, you have a navigation property on category contains all related Movement, i call it Movements in following query. you can write your query like this, with minimum of connection with DB.
class Cat
{
public Guid catId { get; set; }
public string catName { get; set; }
public IEnumerable<Movement> Movements { get; set; }
public int MovementsCount { get { return Movements.Count(); } }
}
var Categories = category.Select(u => new Cat()
{
u.catId,
u.catName,
Movements = u.Movements.AsEnumerable()
}).ToList();
var CategoriesIncludeCount = Categories.OrderBy(u => u.MovementsCount).ToList();

Using LINQ on ObservableCollection with GroupBy and Sum aggregate

I have the following block of code which works fine;
var boughtItemsToday = (from DBControl.MoneySpent
bought in BoughtItemDB.BoughtItems
select bought);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(boughtItemsToday);
It returns data from my MoneySpent table which includes ItemCategory, ItemAmount, ItemDateTime.
I want to change it to group by ItemCategory and ItemAmount so I can see where I am spending most of my money, so I created a GroupBy query, and ended up with this;
var finalQuery = boughtItemsToday.AsQueryable().GroupBy(category => category.ItemCategory);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
Which gives me 2 errors;
Error 1 The best overloaded method match for 'System.Collections.ObjectModel.ObservableCollection.ObservableCollection(System.Collections.Generic.List)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'System.Linq.IQueryable>' to 'System.Collections.Generic.List'
And this is where I'm stuck! How can I use the GroupBy and Sum aggregate function to get a list of my categories and the associated spend in 1 LINQ query?!
Any help/suggestions gratefully received.
Mark
.GroupBy(category => category.ItemCategory); returns an enumerable of IGrouping objects, where the key of each IGrouping is a distinct ItemCategory value, and the value is a list of MoneySpent objects. So, you won't be able to simply drop these groupings into an ObservableCollection as you're currently doing.
Instead, you probably want to Select each grouped result into a new MoneySpent object:
var finalQuery = boughtItemsToday
.GroupBy(category => category.ItemCategory)
.Select(grouping => new MoneySpent { ItemCategory = grouping.Key, ItemAmount = grouping.Sum(moneySpent => moneySpent.ItemAmount);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
You can project each group to an anyonymous (or better yet create a new type for this) class with the properties you want:
var finalQuery = boughtItemsToday.GroupBy(category => category.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});
The AsQueryable() should not be needed at all since boughtItemsToday is an IQuerable anyway. You can also just combine the queries:
var finalQuery = BoughtItemDB.BoughtItems
.GroupBy(item => item.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});

How to perform Join between multiple tables in LINQ lambda

I am trying to perform a Join between multiple tables in LINQ. I have the following classes:
Product {Id, ProdName, ProdQty}
Category {Id, CatName}
ProductCategory{ProdId, CatId} //association table
And I use the following code (where product, category and productcategory are instances of the above classes):
var query = product.Join(productcategory, p => p.Id, pc => pc.ProdID, (p, pc) => new {product = p, productcategory = pc})
.Join(category, ppc => ppc.productcategory.CatId, c => c.Id, (ppc, c) => new { productproductcategory = ppc, category = c});
With this code I obtain an object from the following class:
QueryClass { productproductcategory, category}
Where producproductcategory is of type:
ProductProductCategoryClass {product, productcategory}
I do not understand where the joined "table" is, I was expecting a single class that contains all the properties from the involved classes.
My aim is to populate another object with some properties resulting from the query:
CategorizedProducts catProducts = query.Select(m => new { m.ProdId = ???, m.CatId = ???, //other assignments });
how can I achieve this goal?
For joins, I strongly prefer query-syntax for all the details that are happily hidden (not the least of which are the transparent identifiers involved with the intermediate projections along the way that are apparent in the dot-syntax equivalent). However, you asked regarding Lambdas which I think you have everything you need - you just need to put it all together.
var categorizedProducts = product
.Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
.Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new { ppc, c })
.Select(m => new {
ProdId = m.ppc.p.Id, // or m.ppc.pc.ProdId
CatId = m.c.CatId
// other assignments
});
If you need to, you can save the join into a local variable and reuse it later, however lacking other details to the contrary, I see no reason to introduce the local variable.
Also, you could throw the Select into the last lambda of the second Join (again, provided there are no other operations that depend on the join results) which would give:
var categorizedProducts = product
.Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
.Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new {
ProdId = ppc.p.Id, // or ppc.pc.ProdId
CatId = c.CatId
// other assignments
});
...and making a last attempt to sell you on query syntax, this would look like this:
var categorizedProducts =
from p in product
join pc in productcategory on p.Id equals pc.ProdId
join c in category on pc.CatId equals c.Id
select new {
ProdId = p.Id, // or pc.ProdId
CatId = c.CatId
// other assignments
};
Your hands may be tied on whether query-syntax is available. I know some shops have such mandates - often based on the notion that query-syntax is somewhat more limited than dot-syntax. There are other reasons, like "why should I learn a second syntax if I can do everything and more in dot-syntax?" As this last part shows - there are details that query-syntax hides that can make it well worth embracing with the improvement to readability it brings: all those intermediate projections and identifiers you have to cook-up are happily not front-and-center-stage in the query-syntax version - they are background fluff. Off my soap-box now - anyhow, thanks for the question. :)
What you've seen is what you get - and it's exactly what you asked for, here:
(ppc, c) => new { productproductcategory = ppc, category = c}
That's a lambda expression returning an anonymous type with those two properties.
In your CategorizedProducts, you just need to go via those properties:
CategorizedProducts catProducts = query.Select(
m => new {
ProdId = m.productproductcategory.product.Id,
CatId = m.category.CatId,
// other assignments
});
take look at this sample code from my project
public static IList<Letter> GetDepartmentLettersLinq(int departmentId)
{
IEnumerable<Letter> allDepartmentLetters =
from allLetter in LetterService.GetAllLetters()
join allUser in UserService.GetAllUsers() on allLetter.EmployeeID equals allUser.ID into usersGroup
from user in usersGroup.DefaultIfEmpty()// here is the tricky part
join allDepartment in DepartmentService.GetAllDepartments() on user.DepartmentID equals allDepartment.ID
where allDepartment.ID == departmentId
select allLetter;
return allDepartmentLetters.ToArray();
}
in this code I joined 3 tables and I spited join condition from where clause
note: the Services classes are just warped(encapsulate) the database operations
public ActionResult Index()
{
List<CustomerOrder_Result> obj = new List<CustomerOrder_Result>();
var orderlist = (from a in db.OrderMasters
join b in db.Customers on a.CustomerId equals b.Id
join c in db.CustomerAddresses on b.Id equals c.CustomerId
where a.Status == "Pending"
select new
{
Customername = b.Customername,
Phone = b.Phone,
OrderId = a.OrderId,
OrderDate = a.OrderDate,
NoOfItems = a.NoOfItems,
Order_amt = a.Order_amt,
dis_amt = a.Dis_amt,
net_amt = a.Net_amt,
status=a.Status,
address = c.address,
City = c.City,
State = c.State,
Pin = c.Pin
}) ;
foreach (var item in orderlist)
{
CustomerOrder_Result clr = new CustomerOrder_Result();
clr.Customername=item.Customername;
clr.Phone = item.Phone;
clr.OrderId = item.OrderId;
clr.OrderDate = item.OrderDate;
clr.NoOfItems = item.NoOfItems;
clr.Order_amt = item.Order_amt;
clr.net_amt = item.net_amt;
clr.address = item.address;
clr.City = item.City;
clr.State = item.State;
clr.Pin = item.Pin;
clr.status = item.status;
obj.Add(clr);
}
var query = from a in d.tbl_Usuarios
from b in d.tblComidaPreferidas
from c in d.tblLugarNacimientoes
select new
{
_nombre = a.Nombre,
_comida = b.ComidaPreferida,
_lNacimiento = c.Ciudad
};
foreach (var i in query)
{
Console.WriteLine($"{i._nombre } le gusta {i._comida} y naciĆ³ en {i._lNacimiento}");
}
it has been a while but my answer may help someone:
if you already defined the relation properly you can use this:
var res = query.Products.Select(m => new
{
productID = product.Id,
categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
}).ToList();

Categories