I have a LINQ statement where I'd like to merge in the First address with the Nickname of 'Me'.
using (var ctx = new DataEntities())
{
return from c in ctx.Customers.Include("Addresses")
let m = from a in c.Addresses where a.Nickname == "Me" select a
where m.Any()
select new
{
Id = c.CustomerId,
m.First().Name,
m.First().Address1,
m.First().Address2,
m.First().City,
m.First().State,
m.First().Zip,
m.First().Email,
m.First().PhoneNumber
};
}
I'm wondering:
Is it going to hurt performance if I call First multiple times like this?
Is there an better LINQ statement for this?
Just also realized, Do I need to .Include("Addresses")?
For LINQ to SQL, EF, etc, it probably won't matter - it is entirely possible that the TSQL translation will make it identical anyway. Of course, you can profile to be sure ;-p
But for LINQ-to-Objects (which is very literal) it will. You can improve this by using let:
return from c in ctx.Customers.Include("Addresses")
let m = from a in c.Addresses where a.Nickname == "Me" select a
where m.Any()
let first = m.First()
select new
{
Id = c.CustomerId,
first.Name,
first.Address1,
...
first.PhoneNumber
};
Related
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
Sometimes when I'm writing queries using LINQ and if I use it inside of a loop. It turns so slow the performance.
var query1 = from c in db.Classes
where c.TeacherId.Equals(teacherId)
select c;
// AnsweredAssignment Query
var query2 = (from c in db.AnsweredAssignments
where c.AssignmentId == assignmentId && c.Student.Class.TeacherId.Equals(teacherId)
select c).ToArray();
// Tokens Query
var query3 = (from c in db.Tokens
where c.AssignmentId == assignmentId && c.Student.Class.TeacherId.Equals(teacherId)
select c).ToArray();
// OverwrittenScores Query
var query4 = (from os in db.OverwrittenScores
where os.AssignmentId == assignmentId && os.Student.Class.TeacherId.Equals(teacherId)
select os).ToArray();
foreach (var c in query1)
{
foreach (var s in c.Students)
{
var aaItems = (from aa in query2
where aa.StudentId == s.StudentId
select aa).ToArray();
// Generate scores for objectives
var id3 = (from aa in aaItems
where !aa.IsMakeup
orderby aa.Score descending
select aa).FirstOrDefault();
if (id3 != null)
{
var aa3 = (from aa in query2
where aa.AnsweredAssignmentId == id3.AnsweredAssignmentId
select aa).SingleOrDefault();
...
}
var tokens = (from t in query3
where t.StudentId == s.StudentId
select new MonitorByGeneralScoreToAnsweredAssignment(AssignmentStatus.Pending)).ToList();
...
// does exist any overwritten score?
var osItem = query4.Where(os => os.StudentId == s.StudentId).SingleOrDefault();
...
}
// OverwrittenScores Query
var query4 = (from os in db.OverwrittenScores
where os.AssignmentId == assignmentId && os.Student.Class.TeacherId.Equals(teacherId)
select os).ToArray();
What I'm doing now is to get the records which I'm gonna use instead of getting one by one inside of the loop. Is this a good practice? Sometimes I guess that I'm not doing a good work :(
When I've got the records, I've save it into memory and using LINQ TO OBJECTS (from memory) I get to record.
So remember that making calls to a database will always be slow. In fact, it's often the slowest part of most applications. Thus, you should strive to return a lot of stuff at once, rather than trying to get items one at a time.
Strive to rewrite your queries such that you return as much of the required information in one go as necessary. Although you might use up more memory, it's more often than not worth it for the time savings. Connecting to databases is slow!
Secondly, (last I checked) Entity Framework uses reflection to be able to set properties on your objects. Reflection is also very slow, which is why - despite EFs cool factor - I still prefer to do my queries by hand. The performance is just significantly faster (but of course introduces another layer of complication since now you're not only dealing with one language - C# - but two - C# and SQL - which are conceptually very different).
What I have is a string of comma separated IDs that I'm receiving from a query string (e.g. 23,51,6,87,29). Alternately, that string could just say "all".
In my Linq query I need a way to say (in pseudo code):
from l in List<>
where l.Id = all_of_the_ids_in_csv
&& other conditions
select new {...}
I'm just not sure how to go about doing that. I'm not even sure what to google to get me going in the right direction. Any pointing in the right direction would be extremely helpful.
I would suggest to split your query in 2 - first part will select by ID, and the select one will select other conditions.
First of all: check if query string contains numbers, or is just all:
var IEnumerable<ListItemType> query = sourceList;
if(queryStringValue != "All")
{
var ids = queryStringValue.Split(new[] { ',' })
.Select(x => int.Parse(x)) // remove that line id item.Id is a string
.ToArray();
query = query.Where(item => ids.Contains(item.Id));
}
from l in query
// other conditions
select new {...}
Because LINQ queries have deffered execution you can build queries like that without performance drawback. Query won't be executed until you ask for results (by ToList call or enumeration).
If you really want it with just one LINQ query:
var idArray = all_of_the_ids_in_csv.Split(',');
from l in List<>
where (all_of_the_ids_in_csv == "All" || idArray.Contains(l.Id))
&& other conditions
select new {...}
The trick is using string.Split
var ids = string.split(rawIdString, ",").ToList();
var objects = ids.Where(id=> /*filter id here */).Select(id=>new { /* id will be the single id from the csv */ });
// at this point objects will be an IEnumerable<T> where T is whatever type you created in the new statement above
Im getting a "The method 'Join' is not supported" error... Funny thing is that i simply converted the 1st LINQ into the 2nd version and it doesnt work...
What i wanted to have was LINQ version #3, but it also doesnt work...
This works
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on s["bh_contract"] equals b["bh_contract"]
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
This doesn't
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"] }
equals new { contractid = b["bh_contract"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
Also, this doesn't, which is a composite join and what i really aim for
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"], serviceid = s["serviceid"] }
equals new { contractid = b["bh_contract"], serviceid = s["serviceid"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
I tried early binding and still doesnt work...
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { foo = s.bh_contract.Id }
equals new { foo = b.bh_Contract.Id }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
stil not working
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { s.bh_contract.Id, s.ServiceId }
equals new { b.bh_Contract.Id, ServiceId = b.bh_Service }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
But im simply trying to do the example(s) here How to do joins in LINQ on multiple fields in single join
What am i missing?
Thanks in advance
While I'm not entirely sure which CRM you're using, I think you're misunderstanding something.
In order for a LINQ query to work, there needs to be a LINQ provider for the underlying data source -- the bit of code responsible for translating chain of e.g. Join, Where, operator usage, etc, etc, into the query API of the data source. This might be SQL, some custom query language, or some chain of methods.
Two LINQ providers (such as, one for LINQ to DataSet and some custom provider you've written yourself) don't have to support the same methods and other code. The precise subset of LINQ methods (and/or other embedded statements) a LINQ provider supports is dependent on its implementation.
Looking at it like that, it's not that surprising that the LINQ provider you're using doesn't seem to comprehend the standard syntax for joins using multiple fields, or doesn't seem to comprehend the usage of anonymous types at all.
My advice is to search the documentation of the supplied LINQ provider to see which query operations it supports (perhaps there is a note about this specific mode of query not being supported). Failing that, you'll have to resort to some sort of other query -- one not involving an equijoin. Perhaps your best option is to perform the joins separately, and then intersect the two result groups. It really depends on the specifics of the case.
Have you looked at the MSDN samples. There are some multiple-column join examples there:
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var list_join = (from a in svcContext.AccountSet
join c in svcContext.ContactSet
on a.PrimaryContactId.Id equals c.ContactId
where a.Name == "Contoso Ltd" && <<--- multiple join here
a.Address1_Name == "Contoso Pharmaceuticals"
select a).ToList();
foreach (var c in list_join)
{
System.Console.WriteLine("Account " + list_join[0].Name
+ " and it's primary contact "
+ list_join[0].PrimaryContactId.Id);
}
}
This other thread might be relevant
I'm having trouble coming up with an efficient LINQ-to-SQL query. I am attempting to do something like this:
from x in Items
select new
{
Name = x.Name
TypeARelated = from r in x.Related
where r.Type == "A"
select r
}
As you might expect, it produces a single query from the "Items" table, with a left join on the "Related" table. Now if I add another few similar lines...
from x in Items
select new
{
Name = x.Name
TypeARelated = from r in x.Related
where r.Type == "A"
select r,
TypeBRelated = from r in x.Related
where r.Type == "B"
select r
}
The result is that a similar query to the first attempt is run, followed by an individual query to the "Related" table for each record in "Items". Is there a way to wrap this all up in a single query? What would be the cause of this? Thanks in advance for any help you can provide.
The above query if written directly in SQL would be written like so (pseudo-code):
SELECT
X.NAME AS NAME,
(CASE R.TYPE WHEN A THEN R ELSE NULL) AS TypeARelated,
(CASE R.TYPE WHEN B THEN R ELSE NULL) AS TypeBRelated
FROM Items AS X
JOIN Related AS R ON <some field>
However, linq-to-sql is not as efficient, from your explanation, it does one join, then goes to individually compare each record. A better way would be to use two linq queries similar to your first example, which would generate two SQL queries. Then use the result of the two linq queries and join them, which would not generate any SQL statement. This method would limit the number of queries executed in SQL to 2.
If the number of conditions i.e. r.Type == "A" etc., are going to increase over time, or different conditions are going to be added, you're better off using a stored procedure, which would be one SQL query at all times.
Hasanain
You can use eager loading to do a single join on the server to see if that helps. Give this a try.
using (MyDataContext context = new MyDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Item>(i => i.Related);
context.LoadOptions = options;
// Do your query now.
}