asp.net - linq to sql simple question - c#

Does anyone knows what am I doing wrong, while getting a data from db.
I have the following code
var a = from p in db.test3s
where p.ID == '1'
select p.PostID;
ViewData["a"] = a;
And in the .aspx file the ViewData["a"] shows me this:
SELECT [t0].[PostID] FROM [dbo].[test3] AS [t0] WHERE [t0].[ID] = #p0
...instead of an (some) integer number.

I don't know, what ViewData is, but you need to be aware, that Linq to SQL queries are not executed immediately after you assign them to some variable. It's called lazy loading, and what it means is that you will have your data when you will try to operate on it (e.g. when you will try to iterate over results or sth).
What you want is:
var a = (from p in db.test3s
where p.ID == '1'
select p.PostID).First();
This will get you first result. If you want to get set of results you can call ToList(), ToArray() or something like that.

Try
if(a.Any())
ViewData["a"] = a.First();

You need to iterate over the result before the values become available. Linq2Sql does not know that your query will only return one row (although you may know that). So you could do this instead:
ViewData["a"] = db.test3s.First(t => t.Id == 1).PostID;
Which will make sure that there is only one result, and that the value of PostID is assigned to your view data.

In your example a is of type IQueryable<Int32>. It's like a list of items (but with delayed execution). You should retrieve concrete item using some of selectors: First(), FirstOrDefault(), Single(), SingleOrDefault() and so on (depends what you need in concrete situation)

Related

Nested Linq query returns three times the result?

I am trying to work with dynamic data and running into some odd things with LINQ that I can't find much information online. I want to point out that this issue I run into happens on any nested collection.
I want to take a collection of dynamic data, then filter it with a where query. That where query simply checks all the values to see if it contains "FL" and then I want it to return the dynamic collection... not just the fields that contain FL.
I've explicitly put in the type in the where clause to make it easier to read online, it is redundant otherwise.
IEnumerable<dynamic> query = from agent in agentRecords
from values in (ExpandoObject)agent
where ((KeyValuePair<string, object>)values).Value.ToString().Contains("FL")
select agent;
The query works, but returns 3 times the expected result.(I get 9 agents instead of 3, multiple duplicates.)
I am able to filter it by calling distinct, but something tells me I am not doing this right.
The other way to do this is by using LINQ extension methods
var result = agentRecords.Cast<ExpandoObject>().Where(x => x.Any(y => y.Value.ToString().Contains("FL")));
According to https://learn.microsoft.com/en-us/dotnet/csharp/linq/query-expression-basics, there are multiple examples of "multiple/nested from" linq queries and it doesn't seem to run into this duplicate result problem... what am I overlooking?
Instead of cross joining each agent with its collection of values, test each agent once:
IEnumerable<dynamic> query = from agent in agentRecords
where (from values in (ExpandoObject)agent
select ((KeyValuePair<string, object>)values).Value.ToString().Contains("FL")).Any()
select agent;
Lambda syntax does seem clearer to me, which looks to be identical to your expression:
IEnumerable<dynamic> query2 = agentRecords.Where(agent => ((ExpandoObject)agent).Any(((KeyValuePair<string, object>)values).Value.ToString().Contains("FL")));
from a in agentRecords
where (from i in (ExpandoObject)a
where (((KeyValuePair<string, object>)i).Value.ToString().Contains("FL")
select i).Count() > 0
select a;

How is OrderBy Applied?

I have the following query:
var vendors = (from pp in this.ProductPricings
join pic in this.ProductItemCompanies
on pp.CompanyId equals pic.CompanyId into left
from pic in left.DefaultIfEmpty()
orderby pp.EffectiveDate descending
group pp by new { pp.Company, SortOrder = (pic != null) ? pic.SortOrder : short.MinValue } into v
select v).OrderBy(z => z.Key.SortOrder);
Does anyone know how the last OrderBy() is applied? Does that become part of the SQL query, or are all the results loaded in to memory and then passed to OrderBy()?
And if it's the second case, is there any way to make it all one query? I only need the first item and it would be very inefficent to return all the results.
Well it will try to apply the OrderBy to the original query since you are still using an IQueryable - meaning it hasn't been converted to an IEnumerable or hydrated to a collection using ToList or an equivalent.
Whether it can or not depends on the complexity of the resulting query. You'd have to try it to find out. My guess is it will turn the main query into a subquery and layer on a "SELECT * FROM (...) ORDER BY SortOrder" outer query.
Given your specific example the order by in this situation most, likely be appliead as part of the expression tree when it getting build, there for it will be applied to sql generated by the LINQ query, if you would convert it to Enumarable like ToList as mentioned in another answer then Order by would be applied as an extension to Enumerable.
Might use readable code, because as you write it is not understandable.
You will have a problem in the future with the linq statement. The problem is that if your statement does not return any value the value will be null and whenever you make cause a exception.
You must be careful.
I recommend you to do everything separately to understand the code friend.

Any() not giving what I would expect

delMo is a list of DateTime. I have it populated with 3/1/2014 and 4/1/2014. The results I get back is 2 records (what I expected) but the p.dt values for both records show 3/1/2014. I would expect 1 record to be 3/1/2014 and the other to be 4/1/2014.
My understanding of using Any() in this way is like using an in() statement in SQL. However, the results I get back don't seem to reflect this. Any ideas on what I did wrong here?
var result = (from p in db.Table1
where (from dt in delMo
where p.dt == dt).Any()
&&
(from s in db.Stores
where p.storeID == s.ID).Any()
select p).ToList();
[EDIT]
I broke the problem down further and am getting very strange results. I hardcoded the store ID to 5 and it still gives me the problem. I even loop through delMo and pass in the 1 value to p.dt and on the first pass of 3/1/2014 I get the expected result. The second pass uses 4/1/2014 and it returns 3/1/2014!! If I only have 4/1/2014 in delMo then it works. It's like it's keeping the first 3/1/2014 record for some reason. It makes no sense.
Any returns a bool indicating whether or not any elements in the collection satisfy a given condition. When used without a predicate, it indicates whether or not the collection contains elements. I'm not sure if that's what you meant by saying
in this way is like using in an in() statement
but that's what it does. See this link for more details.
As to your question in the comments, I believe something like this should do it. I'm not a huge fan of using linq over the IEnumerable extensions, so bear with me.
var result = db.Table1.Where( p =>
delMo.Contains( p.dt ) &&
db.Stores.Where( s=> s.ID == p.storeId ) );
This will return the values from db.Table1 where p.dt is in delMo and p.storeId is in db.Stores. Hope this helps!
This should help. I wonder if P has a Stores collection, if it has, then it is more straightforward, but I'm not making assumptions.
(from p in db.Table1
where delMo.Contains(p.dt)
&& db.Stores.Any(x=>x.ID==p.storeID)
select p).ToList();
Note: Date should be exact match(including time) for this to work.

Concatenating three lists into one with LINQ throws an exception

Ok, I must be doing something dumb, but shouldn't this work? I have following three lists:
var commonViews = (from v in context.TPM_VIEWS where v.VIEWID < 0 select v); // IQueryable<TPM_VIEWS>
var ownedViews = (from v in context.TPM_VIEWS where v.OWNERID == userId && v.VIEWID > 0 select v); // IQueryable<TPM_VIEWS>
var sharedViews = (from v in context.TPM_USER.Include("TPM_VIEWS2") where v.USERID == userId select v).First().TPM_VIEWS2; // EntityCollection<TPM_VIEWS>
Each list has the proper values and count. I can return any one of these lists:
return commonViews.ToList();
And I can return a any two of these lists:
return commonViews.Concat(ownedViews).ToList();
However, when I try to return all three:
return commonViews.Concat(ownedViews).Concat(sharedViews).ToList();
I get the exception:
Unable to create a constant value of type 'Entity.TPM_VIEWS'. Only
primitive types or enumeration types are supported in this context.
What am I doing wrong? All three values are indeed enumerable. Mostly, I'm asking this question because it's the best possible way to guarantee I'll notice the problem 30 seconds after posting.
UPDATE:
I'm 93% sure the problem is here:
var sharedViews = (from v in context.TPM_USER.Include("TPM_VIEWS2") where v.USERID == userId select v).First().TPM_VIEWS2;
This looks like an enumerable list of TPM_VIEWS object, and I can call ToList() on it and get the correct data, but it doesn't play well with the other lists.
UPDATE 2:
This actually works. Points to the person who can tell me why!
commonViews.ToList().Concat(ownedViews.ToList()).Concat(sharedViews.ToList()).ToList();
The problem is that Concat() on an EF IQueryable<T> will turn the entire concatenation into a single query.
When you call .Concat(sharedViews), you're passing a scalar (pre-loaded) collection of your nested entity class.
EF doesn't know how to convert that into a query, so it complains.
You can make it faster by calling AsEnumerable() instead of ToList().
This actually works. Points to the person who can tell me why!
commonViews.ToList().Concat(ownedViews.ToList()).Concat(sharedViews.ToList()).ToList();
That's because each of the original queries is executed separately; you're only concatenating the results in memory. There seems to be a bug in the Entity Framework query translator when you combine the 3 queries, but when you call ToList on each of them, they're no longer EF queries, they're just lists, so they're concatenated using Linq to Objects.

Get LINQ to preload a complete table

I need LINQ to grab a whole table, but this seems not to be working... everytime i select on the values via the pkey, a select is fired again..
So, actually this code:
DataContext dc = new DataContext();
dc.Stores.ToList();
Store st = dc.Stores.SingleOrDefault(p => p.Id == 124671);
is making a
select * from store
at the "ToList()" method and an ADDITIONAL
select * from store where id = 124671
at the selection part below it...
Of course, i want to prevent it to make the second select..
How would i do it? (I DON'T want to store the ToList() result in an additional property like List< Store > )
UPDATE:
Regarding your answers that would mean, that:
Store st = stores.SingleOrDefault(p => p.Id == 124671);
Store st = stores.SingleOrDefault(p => p.Id == 124671);
would trigger 2 selects to the DB, which would make the LINQ-idea useless?! Or what am i getting wrong here?
I thought LINQ would basically save all the data i grabbed in the selects and ONLY performs another request when the data was not found in the "cache".. So, i thought of it like some kind of "magical" storagelayer between my application and the database..
UPDATE #2
Just that you get the idea.. i want to lose the performance at the beginning ( when grabbing all data ) and win it back alter when i select from the "cached" data...
Try this instead:
DataContext dc = new DataContext();
var allStores = dc.Stores.ToList();
Store st = allStores.SingleOrDefault(p => p.Id == 124671);
(I'm assuming it knows that the id is the primary key etc)
Which exact version? That looks like LINQ-to-SQL; in 3.5 (no service packs), yes - the identity manager was a bit rubbish at this. It got better in 3.5 SP1, and is supposedly fixed in 4.0.
In .NET 3.5 SP1, the Single(predicate) approach works IIRC, so perhaps use that and catch block? A try/catch will be faster than a network hop.
From the later connect post:
This bug has now been fixed and will
be included in .NET Framework 4.0. The
optimization to search the cache first
for ID-based lookups will now be done
for
Single/SingleOrDefault/First/FirstOrDefault(predicate)
as well as
Where(predicate).Single/SingleOrDefault/First/FirstOrDefault(),
where predicate has the same
restrictions as before.
What you are doing is getting the result as a list, throw it away, and query the original again. That's why you get the double trips to the database.
The ToList method returns the list as a result, which you can store in a variable and query from:
var stores = dc.Stores.ToList();
Store st = stores.SingleOrDefault(p => p.Id == 124671);
Short answer - you don't need to have the ToList()
Long answer - your ToList() call is totally rundant in the supplied example. When working with Linq, nothing it actually executed on your database server until you actually enumerate the IQueryable.
If you just want to get a single record by PK, then just need to work with the data-context directly:
var theStore = dc.Stores.SingleOrDefault(p => p.Id == 124671);
If you actually want to get all of the records and iterate over them, the you can call ToList() on the table:
var allStores = dc.Stores.ToList()
If all you want is a single record, performing
var allStores = dc.Stores.ToList();
Store st = allStores.SingleOrDefault(p => p.Id == 124671);
is a complete failure. You are bringing all of the results into memory and then get .Net to filter the list, thus losing all benefits of indexes on your database etc.

Categories

Resources