C# Linq Group by Object - c#

I have an issue of using group by in LINQ to SQL statement.
The cod I have is
var combinedItems = (from article in articles
join author in authors
on article.AuthorId equals author.Id into tempAuthors
from tempAuthor in tempAuthors.DefaultIfEmpty()
select new { article , author = tempAuthor});
var groups1 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article into g
select g.Key).ToList();
var groups2 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article.Id into g
select g.Key).ToList();
I tried to group in two different ways. The first way, I group by an object and the second way I just group by a field in one of the objects.
When I run groups1, I got an error saying need to evaluate in client side, while when I use groups2, it works all good. Can I ask what could be wrong? If I want to group by object, is there any way to do it?

In case you want to group by object, as you've not overridden Equals and GetHashCode() in your Article class or implemented IEqualityComparer<Article> you're just getting the default comparison, which checks if the references are equal. So what you need is something like this:
class GroupItemComparer : IEqualityComparer<Article>
{
public bool Equals(Article x, Article y)
{
return x.Id == y.Id &&
x.Name == y.Name;
}
public int GetHashCode(Article obj)
{
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode();
}
}
And then you need to change your query to lambda expression:
var groups1 = combinedItems.GroupBy(c => c.article , new GroupItemComparer())
.Select(c => c.Key).ToList();
In case you got any exception regarding translation your method to SQL, you can use AsEnumerable or ToList methods before your GroupBy method, with this methods after data is loaded, any further operation is performed using Linq to Objects, on the data already in memory.

As others have pointed out, the GroupBy is using reference equality by default, and you could get around it by specifying one or more properties to group by. But why is that an error?
The whole point of the query is to translate your Linq query into SQL. Since object reference equality on the client can't be easily translated to SQL, the translator doesn't support it and gives you an error.
When you provide one or more properties to group by, the provider can translate that to SQL (e.g. GROUP BY article.Id), and thus the second method works without error.

Related

Get properties from second (Right) joined entity with Group by

please I'm working on a ASP.NET MVC project with Entity Framework.
I try to write a Linq Query that give me some data, this Query will join two entities and grouping by data, so the issue is when I try to get the properties of second joined entity I don't see them in Intellisense, I need these properties to select them.
What I try :
var R = (from N in SCHOOL_DB_Context.Con.NT_CTR join S in SCHOOL_DB_Context.Con.STGs on N.STG_nt equals S.CD_STG where N.CTR_nt==CTR group N by N.STG_nt into G select new NT_CTR_Anal { /*Here where I want to select some properties from second entity*/ } )
So please any help about this issue ?
Take a look at tis fragment:
group N by N.STG_nt into G
The part between by and into is the key(s), and similar to SQL there you have access to all aliases (variables) from from and join clauses. The name after into is LINQ specific and represents the alias (variable) for accessing the GroupBy result. But what is between group and 'by'? There is no SQL equivalent.
Well, the result of GroupBy is of type IGrouping<TKey, TElement>, which has property TKey Key and also is IEnumerable<TElement>. The TKey is comong from the expression between by and into, while TElement (which is what you can access through into variable is coming from the expression between group and by.
In your sample you've put N there, that`s why you have access only to its properties.
In order to have access to other properties, you would use the typical LINQ construct for "composite" things, which is anonymous type projection, e.g.
var R = (
from N in SCHOOL_DB_Context.Con.NT_CTR
join S in SCHOOL_DB_Context.Con.STGs on N.STG_nt equals S.CD_STG
where N.CTR_nt == CTR
group new { N, S } by N.STG_nt into G
select new NT_CTR_Anal
{
G.Key, // N.STG_nt
SomeNPropSum = G.Sum(e => e.N.SomeNProp),
SomeSPropSum = G.Sum(e => e.S.SomeSProp),
};
As you can see, now you have access to both N and S properties inside grouping aggregate methods.

Linq join on two values

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

linq join on guid and string column

I'm new to linq. I need to run a query that joins two columns (AnonymousUser.AnonymousId being uniqueidentifier and comment.UserId being nvarchar(100)), something like below:
using (CommentEntities db = new CommentEntities())
{
// filteredComments is a query that is not run until the next .ToList()
IQueryable<Comment> filteredComments = this.CommentGetList(...);
var query = from comment in filteredComments
// following line is the syntax error, because columns' types don't match
join user in db.AnonymousUsers on comment.UserId equals user.AnonymousId into gj
from userNull in gj.DefaultIfEmpty()
select new CommentWithName
{
Comment = comment,
UserId = comment.UserId,
FirstName = (userNull == null ? "" : userNull.Name),
LastName = "",
Email = (userNull == null ? "" : userNull.Email)
};
return query.ToList();
}
First I was happy writing the query with .ToString() ! As it turns out that entity framework doesn't know how to translate it to sql. The same is true for Guid.Parse(string). Also new Guid(string) cannot be used in linq to entities (only parameterless constructors allowed)!
So after searching, I found out it's not possible doing such thing in EF 4.0! I migrated my code to a stored procedure that I'm not really happy about it.
Is it possible to tell entity framework to use a CAST in SQL?
Is there any solutions to this problem? Is there any way that I can bring the logic in code?
NOTE: I meant to do it in one GO. Otherwise one possible solution is to get Entities from first table, and put the Ids in a list and get entities from second table.
call toList() before applying those methods. Like:
var Product = db.Products.Where(p => p.ProductId == Guid.Parse("B4E913F9-166C-49BA-AADE-6DB889D1756F")).Single();
Would throw a
c# LINQ to Entities does not recognize the method "System.Guid Parse" (System.String)' method, and this method cannot be translated into a store expression
But this works:
var Product = db.Products.ToList().Where(p => p.ProductId == Guid.Parse("B4E913F9-166C-49BA-AADE-6DB889D1756F")).Single()
p.s.: I think you will lose lazyloading but you can do eagerloading with .Include before calling .ToList().
If your list is object list you could convert it to the type which has Guid as identifier, first create new anonymous type and then filter it base on UserId, sure UserId which is of type int, wont include in join:
int output = 0;
var secondList = list.Where(x=>!int.TryParse(x.UserID, out output))
.Select(x=>new {Comment = x, ID = new Guid(x.UserID))
.ToList();
Now you could run your query on db by using secondList.

Get distinct records using linq to entity

Hi I'm using linq to entity in my application. I need to get distinct records based on one column value "Name"
So I have a table similar like you can see below:
(User)
ID
Name
Country
DateCreated
I need to select all this items but uniques based on Name (unique). Is it possible to accomplish using linq, if so please show me how.
var items = (from i in user select new {i.id, i.name, i.country, i.datecreated}).Distinct();
The Distinct() method doesn't perform well because it doesn't send the DISTINCT SQL predicate to the database. Use group instead:
var distinctResult = from c in result
group c by c.Id into uniqueIds
select uniqueIds.FirstOrDefault();
LINQ's group actually creates subgroups of entities keyed by the property you indicate:
Smith
John
Mary
Ed
Jones
Jerry
Bob
Sally
The syntax above returns the keys, resulting in a distinct list. More information here:
http://imar.spaanjaars.com/546/using-grouping-instead-of-distinct-in-entity-framework-to-optimize-performance
The purely LINQ way that occurs is to group by name, select distinct groups by key, then select based on that.
from i in user
group new {i.ID, i.Country, i.DateRecord} by i.Name into byNmGp
select byNmGp.First();
Edit: Entity Framework is of course a very popular linq provider, but it doesn't handle First() well here, though the logically equivalent (in this case) FirstOrDefault() will work fine. I prefer First() when not forced into FirstOrDefault() by EF's limitations, because its meaning better matches what is sought here.
Another way is to define a helper class:
private class MyRecord : IEquatable<MyRecord>
{
public int ID;
public string Name;
public string Country;
public DateTime DateCreated;
public bool Equals(MyRecord other)
{
return Name.Equals(other.Name);
}
public override bool Equals(object obj)
{
return obj is MyRecord && Equals((MyRecord)obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
/*...*/
var items = (from i in user select new MyRecord {i.ID, i.Name, i.Country, i.DateRecord}).Distinct();
This simply defines distinct differently. Performance will differ by whether the query provider can interpret that definition of equality or not. Convenience will differ based on whether you've similar LINQ queries doing much the same thing or not.
You can use something like this:
var distinctReports = reports.Select(c => c.CompanyCode)
.Distinct()
.Select(c => reports.FirstOrDefault(r => r.CompanyCode == c))
.ToList();
Here's another variation I ended up using which was based off the response from Svetlana. Shows an example of populating a GridView control with unique values. Thanks!
dataGridView_AnalyzeTestSuites.DataSource = (
from tr in _db.TestResults
where tr.TaskId == taskId
select new { TestSuiteName = tr.Test.TestSuite.Name }
).Distinct().ToList();
Hi here is how you can select distinct records with inner join. Hope it helps
var distinctrecords =
(entity.Table.Join(entity.Table2, x => x.Column, y => y.Column, (x, y) => new {x, y})
.Select(#t => new {#t.x.Column2, #t.y.Column3}))
.GroupBy(t => t.Column2)
.Select(g => g.FirstOrDefault());

LINQ to Entity: using Contains in the "select" portion throws unexpected error

I've got a LINQ query going against an Entity Framework object. Here's a summary of the query:
//a list of my allies
List<int> allianceMembers = new List<int>() { 1,5,10 };
//query for fleets in my area, including any allies (and mark them as such)
var fleets = from af in FleetSource
select new Fleet
{
fleetID = af.fleetID,
fleetName = af.fleetName,
isAllied = (allianceMembers.Contains(af.userID) ? true : false)
};
Basically, what I'm doing is getting a set of fleets. The allianceMembers list contains INTs of all users who are allied with me. I want to set isAllied = true if the fleet's owner is part of that list, and false otherwise.
When I do this, I am seeing an exception: "LINQ to Entities does not recognize the method 'Boolean Contains(Int32)' method"
I can understand getting this error if I had used the contains in the where portion of the query, but why would I get it in the select? By this point I would assume the query would have executed and returned the results. This little ditty of code does nothing to constrain my data at all.
Any tips on how else I can accomplish what I need to with setting the isAllied flag?
Thanks
This poached from a previous answer...
Contains not supported.
IN and JOIN are not the same operator (Filtering by IN never changes the cardinality of the query).
Instead of doing it that way use the join method. It's somewhat difficult to understand without using the query operators, but once you get it, you've got it.
var foo =
model.entitySet.Join( //Start the join
values, //Join to the list of strings
e => e.Name, // on entity.Name
value => value, //equal to the string
(ModelItem ent, String str) => ent);//select the entity
Here it is using the query operators
var foo = from e in model.entitySet
join val in values on
e.Name equals val
select e;
Basically the entity framework attempts to translate your LINQ query into a SQL statement but doesn't know how to handle the Contains.
What you can do instead is retrieve your fleets from the database and set the isAllied property later:
var fleets = (from af in FleetSource
select new Fleet
{
fleetID = af.fleetID,
fleetName = af.fleetName,
userId = af.userId
}).AsEnumerable();
foreach (var fleet in fleets)
{
fleet.isAllied = (allianceMembers.Contains(fleet.userID) ? true : false);
}
Everyone above me is wrong!!! (No offense ...) It doesn't work because you are using the IList overload of "Contains" and not the IEnumerable overload of "Contains". Simply change to:
allianceMembers.Contains<int>(af.userID)
By adding the <int>, you are telling the compiler to use the IEnumerable overload instead of the IList overload.
var fleets = from af in FleetSource;
var x = from u in fleets.ToList()
select new Fleet
{
fleetID = u.fleetID,
fleetName = u.fleetName,
isAllied = (allianceMembers.Contains(u.userID) ? true : false)
}
calling ToList() on fleets the query is executed, later you can use Contains().

Categories