LINQ grouping - get other, not grouped properties - c#

I have little problem with my LINQ query (nHibernate)
I need to have count of objects znak with equal property Symbol
My query:
var tmp = (from znak in sesja.Query<Znak>()
group znak by znak.Symbol into r
select new { Name= r.Key.Name, SUM= r.Count() });
This query works, but I need to make object contains other properties of znak class.
In this case: select new { Name= r.Key.Name, SUM= r.Count() }); i can make new objects only from r.Key, (Symbol property). But I need other properties in my new object.
Is it possible ?

I recommend using lambda Linq syntax:
var items = sesja.Query<Znak().AsEnumerable();
var newList = items.GroupBy(x=>x.Symbol).Select(
x=> new { Name=x.Key.Name, Count = x.Count(), Items = x.ToList() });
read more about Linq syntax LINQ: Dot Notation vs Query Expression
I think that lambda syntax is more readable and looks much cleaner in code because it's more c# style not sql style.
Of course there will be no difference in IL code, always you can install tools like resharper, they can convert lambda syntax to sql-like syntax.

Try something like
var tmp = (from znak in sesja.Query<Znak>()
group znak by znak.Symbol into r
select new { Name= r.Key.Name, SUM= r.Count(), Items = r.ToList() });
Items property will contain actual objects in the group.

Related

What is the linq equivalent of the below sql query

select Productid from categories where `categoryname` in `('abc','def','ghi')`;
I have tried this:
var res = from catg in db.Categories where catg.CategoryId.ToString().Contains(SelectedProducts) select catg;
But this doesnt seem to work...
Assuming SelectedProducts is an array of product ids (integers):
var cats = db.Categories.Where(o => SelectedProducts.Contains(o.CategoryId));
var pids = cats.Select(o => o.ProductId);
Reason: SQL IN operator is implemented oppositely in LINQ to SQL. The question highlights a common mistake in LINQ developers trying to translate from SQL, expecting an [attribute] [operator] [set] syntax.
Using an abstract set language we can highlight syntax differences
SQL uses a "Element is included in Set" syntax
LINQ uses a "Set contains Element" syntax
So any IN clause must be reverted using the Contains operator. It will translate to attribute IN (SET) anyways.
You need to use Contains on SelectedProducts
var res = from catg in db.Categories where
SelectedProducts.Contains(catg.categoryname) select catg.Productid;
Using method notation
var res = db.Categories.Where(catg => SelectedProducts
.Contains(catg.categoryname)).Select(catg.Productid);
The equivalence of a SQL IN with IEnumerable.Contains():
var res = from catg in db.Categories
where new[] {"abc","def","ghi"}.Contains(catg.categoryname)
select catg.Productid
Or lambda
db.Categories.Where(x => new[] {"abc","def","ghi"}.Contains(x.categoryname)).Select(c => c.ProductId);

c# only show numbers after decimal point with linq

I have a linq query that executes successfully, one of the columns returned is a decimal type that is used to represent prices in pounds and pence (there will never be any negative values)
I want to be able to strip out the pounds and pence into separate Properties of my projection, however when using functionality such as
var result= from j in context.Products
select
new{
Price = t.Price,
PricePounds = Math.Truncate(t.Price)
};
I get an error that Math.truncate is not supported as it cannot be translated into a store expression. How can I get the pounds value from this query?
If you don't need to do anything else in the database after that, the simplest approach is just to perform the truncation client-side:
var query = context.Products
.AsEnumerable() // Everything from here is LINQ to Objects
.Select(p => new {
p.Price,
PricePounds = Math.Truncate(p.Price)
});
Note that you might also want to just cast to int - and that might be supported in EF already.
EDIT: As noted in comments, you may want to perform a projection first, e.g.
var query = context.Products
.Select(p => new { p.Price, p.SomethingElse })
.AsEnumerable() // Everything from here is LINQ to Objects
.Select(p => new {
p.Price,
PricePounds = Math.Truncate(p.Price),
p.SomethingElse
});
(Where SomethingElse is another property you're interested in, as an example - I doubt that you only want the price.)
This will avoid the entire entity being fetched when you only want a few properties.
You may try:
var result= from j in context.Products
select
new {
Price = t.Price,
PricePounds = EntityFunctions.Truncate(t.Price, 0)
};
The case is Math.Truncate cannot be translated into SQL where EntityFunctions.Truncate should be.

Entity Framework - Join to a List

I need to retrieve a list of entities from my database that matches a list of items in a plain list (not EF). Is this possible with Entity Framework 4.1?
Example:
var list = new List<string> { "abc", "def", "ghi" };
var items = from i in context.Items
where list.Contains(i.Name)
select i;
This works great to return rows that match one property, but I actually have a more complex property:
var list = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
// i need to write a query something like this:
var items = from i in context.Items
where list.Contains(new Tuple<string,string>(i.Name, i.Type))
select i;
I know that is not valid because it will say it needs to be a primitive type, but is there any way to do what I'm trying to accomplish or will I need to resort to a stored procedure?
You have a few options:
1) You could, of course, write a stored procedure to do what you need and call it.
2) You could read the table into memory and then query the in memory list...that way you don't have to use primitives:
var items = from i in context.Items.ToList()
where list.Contains(new Tuple<string, string>(i.Name, i.Type))
select i;
3) You could also convert your query to use primitives to achieve the same goal:
var items = from i in context.Items
join l in list
on new { i.Name, i.Type } equals
new { Name = l.Item1, Type = l.Item2 }
select i;
I would go with the second option as long as the table isn't extremely large. Otherwise, go with the first.
You need to break it down to sub-properties. For example, something like (this might not compile, i'm not able to test at the moment, but it should give you something to work with):
var items = from i in context.Items
where list.Select(x => x.Item1).Contains(i.Name)
&& list.Select(x => x.Item2).Contains(i.Type)
select i;
You have to think about what the resulting SQL would look like, this would be difficult to do directly in SQL.
My suggestion would be you split out one field of the tuples and use this to cut down the results list, get back the query result then filter again to match one of the tuples e.g.
var list = new List<string> { "abc", "def" };
var list2 = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
var items = (from i in context.Items
where list.Contains(i.Name)
select i)
.AsEnumerable()
.Where(i => list2.Any(j => j.val1 == i.Name && j.val2 == i.Type);

Linq with a long where clause

Is there a better way to do this? I tried to loop over the partsToChange collection and build up the where clause, but it ANDs them together instead of ORing them. I also don't really want to explicitly do the equality on each item in the partsToChange list.
var partsToChange = new Dictionary<string, string> {
{"0039", "Vendor A"},
{"0051", "Vendor B"},
{"0061", "Vendor C"},
{"0080", "Vendor D"},
{"0081", "Vendor D"},
{"0086", "Vendor D"},
{"0089", "Vendor E"},
{"0091", "Vendor F"},
{"0163", "Vendor E"},
{"0426", "Vendor B"},
{"1197", "Vendor B"}
};
var items = new List<MaterialVendor>();
foreach (var x in partsToChange)
{
var newItems = (
from m in MaterialVendor
where
m.Material.PartNumber == x.Key
&& m.Manufacturer.Name.Contains(x.Value)
select m
).ToList();
items.AddRange(newItems);
}
Additional info: I am working in LINQPad and this is a LinqToSql query. Here MaterialVendor is both a class and a DataContext Table.
Edit: LinqToSql details.
This seems to be the best method that I have found for both readability and reducing the complexity. It also has the added benefit of not having the collection type defined explicitly. That means I can vary what comes back with an anonymous type.
var predicate = PredicateBuilder.False<MaterialVendor>();
foreach (var x in partsToChange)
{
var item = x;
predicate = predicate.Or (m =>
m.Material.PartNumber == item.Key
&& m.Manufacturer.Name.Contains(item.Value));
}
var items = from m in MaterialVendor.Where(predicate)
select m;
[Edit] Even better, since partsToChange is a Dictionary:
var items = MaterialVendor.Where(m =>
m.Manufacturer.Name.Contains(partsToChange[m.Material.PartNumber])
).ToList();
Look into PredicateBuilder
This will allow you to build a Linq to sql expression within a loop, adding the clauses with AND / OR where necessary, then execute it once at the end.
The where clause size doesn't really matter. The querying within a loop is the part that drives maintainability and performance down.
List<MaterialVendor> items =
(
from z in MaterialVendor
let partKey = z.Material.PartNumber
where partsToChange.ContainsKey(partKey)
let partValue = partsToChange[partKey]
where z.Manufacturer.Name.Contains(partValue)
select z
).ToList();
Now that we know that linq to sql is involved... here's a mixed mode query.
List<string> keys = partsToChange.Keys.ToList();
List<MaterialVendor> items =
(
from z in MaterialVendor
let partKey = z.Material.PartNumber
where keys.Contains(partKey)
select new {MatVendor = z, Name = z.Manufacturer.Name, Key = partKey}
).AsEnumerable()
.Where(x => x.Name.Contains(partsToChange[x.partKey]))
.Select(x => x.MatVendor)
.ToList();

Linq: using StringComparer with GroupBy/Distinct in query syntax

I have this (XLinq) query and was wondering how to convert it to the query syntax:
var grouped = doc.Descendants()
.GroupBy(t => t.Element(ns + "GroupingAttr").Value, StringComparer.OrdinalIgnoreCase);
This is the query syntax without the StringComparer:
var grouped = from t in doc.Descendants()
group t by t.Element(ns + "GroupingAttr").Value into group
select group
My groupby is a little more complicated than this, so I prefer to use the key of the group instead of introducing a new property.
This is what I tried, but doesn't work because the let "key" is not available in the context of the select (I've uses my more complicated key definition to illustrate the fact I don't want to repeat this in the select):
var grouped = from t in doc.Descendants()
let key = ((t.Name != ns + "SomeElementName") ? t.Element(ns + "SomeAttribute") : t.Element(ns + "SomeOtherAttribute")).ElementValueOrDefault("Empty group")
group t by key.ToUpper() into g
select new { Name = key, Items = g };
In the end, case-sensitivity was not important because I could presume that all casings were the same...
Related question: LINQ Distinct operator ignore case?
I don't think you can use the comparer within the query syntax, however you could call ToUpper on your value. This will then ignore case for you. As a side note using ToUpper is more efficient than using ToLower, so ToUpper would be the way to go.
The C# team were very sparse with what they introduced into the query syntax, so for anything like this you'll have to use the extension methods syntax.
var grouped = from t in doc.Descendants()
group t by t.Element(ns + "GroupingAttr").Value into MyGroup
select MyGroup.Key

Categories

Resources