Maybe found a feature in Linq. Groupby with multiple fields - c#

These two rows seems to do the same thing. A plussign (+) can be used instead of an anonymous type.
var newlist1 = list.GroupBy(x => x.FIELD1 + x.FIELD2).Select(y => y.First());
var newlist2 = list.GroupBy(x => new {x.FIELD1, x.FIELD2}).Select(y => y.First());
Now my question:
Is the plussign (+) something thats documented for GroupBy?

be careful of this :)
If for example x.FIELD1 and x.FIELD2 are properties of type string, you're just grouping by the result of concatenating the two.... which is probably not what you want. Same applies for other types of course, but an example in strings still:
Given Field1 = "ABC" and Field2 = "DEF", your grouping will be with the key "ABCDEF", right?
So what if you had Field1 = "AB" and Field2 = "CDEF"? Very much different values, but your grouping would still be "ABCDEF"...
You should stick to anonymous types for grouping (when used within a method only), or when needed externally, a new class, struct, or make use of a Tuple.
EDIT: Another quick note: after you have executed the GroupBy (without the projection), take a look at the Key values you are getting.... it should show you an example of what I mean.

Related

How to select all objects sharing a property value with a property value in list of objects?

I have a list of objects that I want to reload their data.
Like always, I have several options. I wanted just to select these items but encountered this "Additional information": Unable to create a constant value of type 'Item'. Only primitive types or enumeration types are supported in this context.
// (System.Collections.Generic.List<Item> selectedItems)
System.Collections.Generic.List<Item> items;
var q = from i in db.Items
where selectedItems.Any(s => s.Id == i.Id)
select i;
items = q.ToList()
the following yields the same, as expected...
var q = db.Items.Where(i => selectedItems.Any(si => i.Id == si.Id));
items = q.ToList();
I could have reattached each of the objects and call the reload, but then I would have(or not, but I don't know how) to run across the db lot of times to reload their Navigation Properties.
The only "fine" solution I've found until now is selecting the Id's of selectedItems and then running with it like follows:
int[] itemIds = selectedItems.Select(i => i.Id).ToArray();
var q = db.Items.Where(i => itemIds.Any(iId => i.Id == iId)); //Of course `Contains` could be used instead of `Any` here, since `itemIds` is a simple array of integers
items = q.ToList();
But is it a necessity or there is a more straight forward, neat or proper way to accomplish this?
But is it a necessity or there is a more straight forward, neat or proper way to accomplish this?
Not that I can think of. EF will try and turn your where clause into SQL (which is not as easy as you'd think). When it parses the expression and encounters a call to Any on a collection of non-primitive types, it does not know how to generically convert that list into a list of values to put in an into an in clause and gives you the error you quoted.
When the source collection is a collection of primitive or enumeration types, it can turn the source collection into a list of values and create an in clause. Contains does the same thing (it is also shorter is closer to the intent IMHO):
var q = db.Items.Where(i => itemIds.Contains(i.Id));

Intersect two collections which contain different types

Suppose I have one collection, call it ids it is of type IEnumerable<string>, I have a second collection call it objects it's of type MyObject[]. MyObject has a string property called id. I would like a LINQ statement that returns all off the objects in the objects collection who's id matches any value in the ids collection. ids will be a strict subset of objects.Select(x => x.id). Meaning, for every string in ids I know there will be exactly one corresponding MyObject in objects. Can someone post a pure LINQ solution? I've tried a couple things with no luck. I can come up with an iterative solution easily enough so unless it's impossible to do with only LINQ please don't post any.
"Just" LINQ:
var r = obj.Where(o => ids.Any(id => id == o.id));
But better, for larger n, with a set:
var hs = new HashSet(ids);
var r = obj.Where(o => hs.Contains(o.id));
I think this is pretty straightforward with query syntax.
It would look something like:
var a = from o in objects
join i in ids on o.id equals i
select o;
If you just want a list of MyObject that match, you can do :
var solution = objects.Where(x=> ids.Contains(x.id));
With this instead, you'll get a List<T> where T is an Anonymous type with 2 properties, Id that is the string that work as "key" in this specific case, and Obj, a list of MyObject which id correspond to the Id property.
var solution = ids.Select(x=>new{ Id = x, Obj=objects.Where(y=>y.id == x).ToList()})
.ToList();
If you just want to know if there is any object in the intersection (which was what I was looking for)
Based on this
var a = from o in objects
join i in ids on o.id equals i
select o;
You can do this as well
var isEmpty = objects.Any(x => ids.Any(y => y == x.ToString()));
The accepted answer is correct. However, if someone doesn't like using SQL style LINQ, here is the LINQ extension method approach to solving the same problem.
var filteredObjects = objects.Join(ids, obj => obj.Id, id => id, (obj, _) => obj);
We are joining two different types, so the 2nd & 3rd Join parameter signify that join will be made on id.
The fourth parameter is used to select an object out of the resultant (obj, id) pair after applying join.

LINQ Where clause with Contains where the list has complex object

I've seen plenty of examples of LINQ with a contains on a simple list of objects:
var intList= new List<int>() { 1, 2, 3 };
var result = db.TableRecords.Where(c => intList.Contains(c.RecordId)).ToList();
What I'm trying to do seems slightly more complicated (I think). I have a line of code similar to this one gets me the list I need:
var xzList = db.Relations.Where(r => someOtherList.Contains(r.zId))
.Select(r => new { AId = r.xId, BId = r.zId })
.ToList();
And now I want to get the result similar to the previous example but the list now has an anonymous type in it with two ints. So how would I now get result where RecordId in TableRecords equals the AId in the anonymous type for each anonymous type in xzList?
Sounds like you are unsure how to get the values out of your anonymous type. You can use GunnerL3510's solution to dump it to a list, or you should be able to inline it like this:
var result =
db.TableRecords
.Where(c => xzList.Select(n => n.AId)
.Contains(c.RecordId))
.ToList();
Since you are naming the values in your anonymous type, you refer to them just like properties.
If you prefer to do a more structured approach, you can use this method.
Something like this:
db.TableRecords.Select(c=>c.RecordId).Intercept(xzList.Select(n => n.AId)).Any()

2 arrays into 1, with lambda exp distinct

I've got 2 object arrays that Im getting from the DB, some overlap, so I have to make a distinct func.
I tried to make a lambda expression , but I still got an overlapped objects.
this is my code:
ArtObject[] pinui = new ArtObject[root.Count - 1];
ArtObject[] c1= new ArtObject[root2.Count - 1];
pinui = getArticlesArray(root2, pinui);
c1= getArticlesArray(root, c1);
art = new ArtObject[c1.Count()+pinui.Count()];
pinui.CopyTo(art, 0);
c1.CopyTo(art, pinui.Count());
art = art.Distinct().OrderByDescending(a => a.dateTosort).ToArray();
I guess something wrong with my last line, art = art.Distinct().OrderByDescending(a => a.dateTosort).ToArray(); .. I wonder what and how can I get only the distinct objects..?
Distinct will use Equals and GetHashCode to determine equal values. I suspect you haven't overridden these methods to indicate how you want equality to be checked.
Also note that your last part would be simpler as:
ArtObject[] art = pinui.Union(c1).OrderByDescending(a => a.dateTosort).ToArray();
a.Union(b) is equivalent to a.Concat(b).Distinct().
An alternative to overriding GetHashCode and Equals is to specify an IEqualityComparer<Person> to either Union or Distinct.
MoreLINQ makes this easier with a DistinctBy method:
var query = collection.DistinctBy(x => x.Description);
(There's no equivalent for Union yet, but we could easliy add one.)
If you do distinct on one of fields, you can use GroupBy then get First:
pinui.Concat(c1).GroupBy(a => a.Description, (key, g) => g.First())
.OrderByDescending(a => a.dateTosort).ToArray();
Your code looks fine, although it could be shortened (no need to use CopyTo):
var art = pinui.Union(c1).OrderByDescending(a => a.dateTosort).ToArray();
Union already removes duplicates like Distinct.
The reason why the objects are not detected as duplicates is probably because they are distinct. For example, by default,
var obj1 = new ArtObject("Picasso");
var obj2 = new ArtObject("Picasso");
are two distinct objects.
You can, however, provide a custom equality comparer to Distinct or Union, the linked MSDN page contains a nice example.

LINQ flavored IS IN Query

There are quite a few other questions similiar to this but none of them seem to do what I'm trying to do. I'd like pass in a list of string and query
SELECT ownerid where sysid in ('', '', '') -- i.e. List<string>
or like
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => a.sysid IN chiList).Select(a => a.ownerid);
I've been playing around with a.sysid.Contains() but haven't been able to get anywhere.
Contains is the way forward:
var chiLst = new List<string>();
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
Although you'd be better off with a HashSet<string> instead of a list, in terms of performance, given all the contains checks. (That's assuming there will be quite a few entries... for a small number of values, it won't make much difference either way, and a List<string> may even be faster.)
Note that the performance aspect is assuming you're using LINQ to Objects for this - if you're using something like LINQ to SQL, it won't matter as the Contains check won't be done in-process anyway.
You wouldn't call a.sysid.Contains; the syntax for IN (SQL) is the reverse of the syntax for Contains (LINQ)
var parRec = Lnq.attlnks.Where(a => chiList.Contains(a.sysid))
.Select(a => a.ownerid);
In addition to the Contains approach, you could join:
var parRec = from a in Lnq.attlnks
join sysid in chiLst
on a.sysid equals sysid
select a.ownerid
I'm not sure whether this will do better than Contains with a HashSet, but it will at least have similar performance. It will certainly do better than using Contains with a list.

Categories

Resources