I'm developing an Entity Framework 4.4.0.0 Code First library with C# and .NET Framework 4.0.
Previously I had this:
var msgs = from message in context.Messages
where message.TalkId == talkId &&
(message.SentBy == userId || message.SentTo == userId)
orderby message.DateUtcSent
select message;
And now I have this:
var msgs = from message in context.Messages
where message.TalkId == talkId &&
(message.SentBy == userId || message.SentTo == userId)
group message by message.DateUtcSent into grps
select new
{
Value = grps
};
Because I want to avoid repeated rows.
Do I still need orderby message.DateUtcSent with this group by expression?
How I can add it (if I need it)?
Without an order-by you don't have any ordering guarantees. If you need the guarantee, add the order-by. Without order-by any observed order is purely coincidental.
You can add an order-by by adding orderby grps.Key. Feel free to ask follow-ups in the comments.
Related
I have some errors using Linq on DataTable and I couldn't figure it out how to solve it. I have to admit that i am pretty new to Linq and I searched the forum and Internet and couldn't figure it out. hope you can help.
I have a DataTable called campaign with three columns: ID (int), Product (string), Channel (string). The DataTable is already filled with data. I am trying to select a subset of the campaign records which satisfied the conditions selected by the end user. For example, the user want to list only if the Product is either 'EWH' or 'HEC'. The selection criteria is dynaically determined by the end user.
I have the following C# code:
private void btnClick()
{
IEnumerable<DataRow> query =
from zz in campaign.AsEnumerable()
orderby zz.Field<string>("ID")
select zz;
string whereClause = "zz.Field<string>(\"Product\") in ('EWH','HEC')";
query = query.Where(whereClause);
DataTable sublist = query.CopyToDataTable<DataRow>();
}
But it gives me an error on line: query = query.Where(whereClause), saying
No property or field 'zz' exists in type 'DataRow'".
If I changed to:
string whereClause = "Product in ('EWH','HEC')"; it will say:
No property or field 'Product' exists in type 'DataRow'
Can anyone help me on how to solve this problem? I feel it could be a pretty simple syntax change, but I just don't know at this time.
First, this line has an error
orderby zz.Field<string>("ID")
because as you said, your ID column is of type int.
Second, you need to learn LINQ query syntax. Forget about strings, the same way you used from, orderby, select in the query, you can also use where and many other operators. Also you'll need to learn the equivalent LINQ constructs for SQL-ish things, like for instance IN (...) is mapped to Enumerable.Contains etc.
With all that being said, here is your query
var productFilter = new[] { "EWH", "HEC" };
var query =
from zz in campaign.AsEnumerable()
where productFilter.Contains(zz.Field<string>("Product"))
orderby zz.Field<int>("ID")
select zz;
Update As per your comment, if you want to make this dynamic, then you need to switch to lambda syntax. Multiple and criteria can be composed by chaining multiple Where clauses like this
List<string> productFilter = ...; // coming from outside
List<string> channelFilter = ...; // coming from outside
var query = campaign.AsEnumerable();
// Apply filters if needed
if (productFilter != null && productFilter.Count > 0)
query = query.Where(zz => productFilter.Contains(zz.Field<string>("Product")));
if (channelFilter != null && channelFilter.Count > 0)
query = query.Where(zz => channelFilter.Contains(zz.Field<string>("Channel")));
// Once finished with filtering, do the ordering
query = query.OrderBy(zz => zz.Field<int>("ID"));
I have the following LINQ query using EF5 and generic repository, unit of work patterns to a SQL Server 2008 db
var countriesArr = GetIdsFromDelimStr(countries);
var competitionsArr = GetIdsFromDelimStr(competitions);
var filterTeamName = string.Empty;
if (teamName != null)
{
filterTeamName = teamName.ToUpper();
}
using (var unitOfWork = new FootballUnitOfWork(ConnFooty))
{
// give us our selection of teams
var teams =
(from team in
unitOfWork.TeamRepository.Find()
where ((string.IsNullOrEmpty(filterTeamName) || team.Name.ToUpper().Contains(filterTeamName)) &&
(countriesArr.Contains(team.Venue.Country.Id) || countriesArr.Count() == 0))
select new
{
tId = team.Id
}).Distinct();
// give us our selection of contests
var conts = (
from cont in
unitOfWork.ContestRepository.Find(
c =>
((c.ContestType == ContestType.League && competitionsArr.Count() == 0) ||
(competitionsArr.Contains(c.Competition.Id) && competitionsArr.Count() == 0)))
select new
{
contId = cont.Id
}
).Distinct();
// get selection of home teams based on contest
var homecomps = (from fixt in unitOfWork.FixtureDetailsRepository.Find()
where
teams.Any(t => t.tId == fixt.HomeTeam.Id) &&
conts.Any(c => c.contId == fixt.Contest.Id)
select new
{
teamId = fixt.HomeTeam.Id,
teamName = fixt.HomeTeam.Name,
countryId = fixt.HomeTeam.Venue.Country.Id != null ? fixt.HomeTeam.Venue.Country.Id : 0,
countryName = fixt.HomeTeam.Venue.Country.Id != null ? fixt.HomeTeam.Venue.Country.Name : string.Empty,
compId = fixt.Contest.Competition.Id,
compDesc = fixt.Contest.Competition.Description
}).Distinct();
// get selection of away teams based on contest
var awaycomps = (from fixt in unitOfWork.FixtureDetailsRepository.Find()
where
teams.Any(t => t.tId == fixt.AwayTeam.Id) &&
conts.Any(c => c.contId == fixt.Contest.Id)
select new
{
teamId = fixt.AwayTeam.Id,
teamName = fixt.AwayTeam.Name,
countryId = fixt.AwayTeam.Venue.Country.Id != null ? fixt.AwayTeam.Venue.Country.Id : 0,
countryName = fixt.AwayTeam.Venue.Country.Id != null ? fixt.AwayTeam.Venue.Country.Name : string.Empty,
compId = fixt.Contest.Competition.Id,
compDesc = fixt.Contest.Competition.Description
}).Distinct();
// ensure that we return the max competition based on id for home teams
var homemax = (from t in homecomps
group t by t.teamId
into grp
let maxcomp = grp.Max(g => g.compId)
from g in grp
where g.compId == maxcomp
select g).Distinct();
// ensure that we return the max competition based on id for away teams
var awaymax = (from t in awaycomps
group t by t.teamId
into grp
let maxcomp = grp.Max(g => g.compId)
from g in grp
where g.compId == maxcomp
select g).Distinct();
var filteredteams = homemax.Union(awaymax).OrderBy(t => t.teamName).AsQueryable();
As you can see we want to return the following format which is passed across to a WebAPI so we cast the results to types we can relate to in the UI.
Essentially what we are trying to do is get the home and away teams from a fixture, these fixtures have a contest which relates to a competition. We then get the highest competition id from the grouping and then this is returned with that team. The country is related to the team based on the venue id, when I was originally doing this i had problems figuring out how to do OR joins in linq which is why i split it down to getting home teams and away team and then grouping them based on competition then unioning them together.
An idea of current table size is fixtures has 7840 rows, teams has 8581 rows, contests has 337 rows and competitions has 96 rows. The table that is likely to increase rapidly is the fixture table as this is related to football.
The output we want to end up with is
Team Id, Team Name, Country Id, Country Name, Competition Id, Competition Name
Using no filtering this query takes on average around 5 secs, just wondering if anybody has any ideas/pointers on how to make it quicker.
thanks in advance Mark
I can't judge whether it will speed up things, but your homemax and awaymax queries could be
var homemax = from t in homecomps
group t by t.teamId into grp
select grp.OrderByDescending(x => x.compId).FirstOrDefault();
var awaymax = from t in awaycomps
group t by t.teamId into grp
select grp.OrderByDescending(x => x.compId).FirstOrDefault();
Further, as you are composing one very large query it may perform better when you cut it up in a few smaller queries that fetch intermediary results. Sometimes a few more roundtrips to the database perform better than one very large query for which the database engine can't find a good execution plan.
Another thing is all these Distinct()s. Do you always need them? I think you can do without because you are always fetching data from one table without joining a child collection. Removing them may save a bunch.
Yet another optimization could be to remove the ToUpper. The comparison is done by the database engine in SQL and chances are that the database has a case-insensitive collation. If so, the comparison is never case sensitive even if you'd want it to be! Constructs like Name.ToUpper cancel the use of any index on Name (it is not sargable).
I'm trying to merge these two object but not totally sure how.. Can you help me merge these two result objects?
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative3 = (from c in ctx.CognosSecurities
where c.SecurityType == 3 || c.SecurityType == 0
select new {c.SecurityType , c.LoginName }).Distinct();
I think I see where to go with this... but to answer the question the types of the objects are int, string, string for SecurityType, LoginName , and SecurityName respectively
If you're wondering why I have them broken like this is because I want to ignore one column when doing a distinct. Here are the SQL queries that I'm converting to SQL.
select distinct SecurityType, LoginName, 'Segment'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =1
select distinct SecurityType, LoginName, 'Business Line'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =2
select distinct SecurityType, LoginName, SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType in (1,2)
You can't join these because the types are different (first has 3 properties in the resulting type, second has two).
If you can tolerate putting a null value in for the 3rd result of the second query this will help. I would then suggest you just do a userListAuthoritative.concat(userListAuthoritative3 ) BUT I think this will not work as the anonymous types generated by the linq will not be of the same class, even tho the structure is the same. To solve that you can either define a CustomType to encapsulate the tuple and do select new CustomType{ ... } in both queries or postprocess the results using select() in a similar fashion.
Acutally the latter select() approach will also allow you to solve the parameter count mismatch by implementing the select with a null in the post-process to CustomType.
EDIT: According to the comment below once the structures are the same the anonymous types will be the same.
I assume that you want to keep the results distinct:
var merged = userListAuthoritative.Concat(userListAuthoritative3).Distinct();
And, as Mike Q pointed out, you need to make sure that your types match, either by giving the anonymous types the same signature, or by creating your own POCO class specifically for this purpose.
Edit
If I understand your edit, you want your Distinct to ignore the SecurityName column. Is that correct?
var userListAuthoritative = from c in ctx.CognosSecurities
where new[]{0,1,2,3}.Contains(c.SecurityType)
group new {c.SecurityType, c.LoginName, c.SecurityName}
by new {c.SecurityType, c.LoginName}
select g.FirstOrDefault();
I'm not exactly sure what you mean by merge, since you're returning different (anonymous) types from each one. Is there a reason the following doesn't work for you?
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2 || c.SecurityType == 3 || c.SecurityType == 0)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
Edit: This assumed they were of the same type -- but they're not.
userListAuthoritative.Concat(userListAuthoritative3);
Try below code, you might need to implement IEqualityComparer<T> in your ctx type.
var merged = userListAuthoritative.Union(userListAuthoritative3);
Hello – I’m trying to get a where condition to apply to a sub collection. I can get the criteria to return the proper parents. However, I want the sub collection to be limited to the criteria as well.
In my example code, I only want people with “LINK” skills; also, I only want the skills for each person to equal “LINK.” That is, each person should only have “LINK” for their skills.
Thanks in advance.
List<Skill> skills = new List<Skill>();
skills.Add(new Skill(){SkillName="ASP.NET"});
skills.Add(new Skill(){SkillName="C#"});
Person p1 = new Person(){ Name="Me", Skills=skills} ;
List<Skill> skills2 = new List<Skill>();
skills2.Add(new Skill(){SkillName="ASP.NET"});
skills2.Add(new Skill(){SkillName="C#"});
skills2.Add(new Skill(){SkillName="LINQ"});
Person p2 = new Person(){ Name="You", Skills=skills2} ;
List<Person> People = new List<Person>();
People.Add(p1);
People.Add(p2);
var oResult = (from item in People
from sk in item.Skills
where sk.SkillName == "LINQ"
select item
).ToList();
When I run this. I get p2 (correct), but I want the skills of P2 to only equal LINQ
Do this:
var oResult = (from item in People
where item.Skills.Count() == 1 &&
item.Skills.Any(s => s.SkillName == "LINQ")
select item
).ToList();
This query will return nothing because p2 (You) has other skills in addition to LINQ.
You can do what you want this way:
foreach (var person in oResult)
{
person.Skills.RemoveAll(s => !s.SkillName.Equals("LINQ"));
}
Note: while using LINQ you're only filtering your data. To post process it you use something like the foreach I show you above.
Try this:
var oResult = (from item in People
where item.Skills != null
where item.Skills.Count() > 0
where item.Skills.All(s => s.SkillName == "LINQ")
select item
).ToList();
Even though your example shows that the Skills collection has a value, you want to make sure that your code doesn't blow up if the Skills property is null.
Also, the All predicate returns true if your list is empty so you need to ensure that it is not empty. The above query reads better, but depending on the implementation of the Skills property calling Count() may cause the entire collection to be iterated. You could use Any() instead to ensure that the collection is not empty.
var oResult = (from item in People
where item.Skills != null
where item.Skills.Any()
where item.Skills.All(s => s.SkillName == "LINQ")
select item
).ToList();
I am looking to optimize my LINQ query because although it works right, the SQL it generates is convoluted and inefficient...
Basically, I am looking to select customers (as CustomerDisplay objects) who ordered the required product (reqdProdId), and are registered with a credit card number (stored as a row in RegisteredCustomer table with a foreign key CustId)
var q = from cust in db.Customers
join regCust in db.RegisteredCustomers on cust.ID equals regCust.CustId
where cust.CustomerProducts.Any(co => co.ProductID == reqdProdId)
where regCust.CreditCardNumber != null && regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.DisplayName,
RegNumber = cust.RegNumber
};
As an overview, a Customer has a corresponding Person which has the Name; PersonID is a foreign key in Customer table.
If I look at the SQL generated, I see all columns being selected from the Person table. Fyi, DisplayName is an extension method which uses Customer.FirstName and LastName. Any ideas how I can limit the columns from Person?
Secondly, I want to get rid of the Any clause (and use a sub-query) to select all other CustomerIds who have the required ProductID, because it (understandably) generates an Exists clause.
As you may know, LINQ has a known issue with junction tables, so I cannot just do a cust.CustomerProducts.Products.
How can I select all Customers in the junction table with the required ProductID?
Any help/advice is appreciated.
The first step is to start your query from CustomerProducts (as Alex Said):
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
join regCust in db.RegisteredCustomers
on custProd.Customer.ID equals regCust.CustId
where
custProd.ProductID == reqProdId
&& regCust.CreditCardNumber != null
&& regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
This will simplify your syntax and hopefully result in a better execution plan.
Next, you should consider creating a foreign key relationship between Customers and RegisteredCustomers. This would result in a query that looked like this:
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
where
custProd.ProductID == reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
Finally, for optimum speed, have LINQ compile your query at compile time, rather than run time by using a compiled query:
Func<MyDataContext, SearchParameters, IQueryable<CustomerDisplay>>
GetCustWithProd =
System.Data.Linq.CompiledQuery.Compile(
(MyDataContext db, SearchParameters myParams) =>
from custProd in db.CustomerProducts
where
custProd.ProductID == myParams.reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
);
You can call the compiled query like this:
IQueryable<CustomerDisplay> myCustDisplay = GetCustWithProd(db, myParams);
I'd suggest starting your query from the product in question, e.g. something like:
from cp in db.CustomerProducts
join .....
where cp.ProductID == reqdProdID
As you have found, using a property defined as an extension function or in a partial class will require that the entire object is hydrated first and then the select projection is done on the client side because the server has no knowledge of these additional properties. Be glad that your code ran at all. If you were to use the non-mapped value elsewhere in your query (other than in the projection), you would likely see a run-time exception. You can see this if you try to use the Customer.Person.DisplayName property in a Where clause. As you have found, the fix is to do the string concatenation in the projection clause directly.
Lame Duck, I think there is a bug in your code as the cust variable used in your select clause isn't declared elsewhere as a source local variable (in the from clauses).