(Please see the answer I wrote for more understanding of the situation.)
Below is a query which works great, operating on selected rows of table STUDENTS. Then one edit destroys the query variable. What's wrong here?
students is rows selected from an import Datatable defined in part by:
importTable.Columns.Add("SECTION", typeof(string));
importTable.Columns.Add("NUMBER", typeof(string));
importTable.Columns.Add("ID", typeof(string));
(Because the DataTable is untyped, I need to cast the data into string to use the fields).
Then called by:
IEnumerable<DataRow> s = importTable.AsEnumerable();
IEnumerable<DataRow> t = s
.OrderBy(r => r["HALL"]);
IEnumerable<DataRow> sortedTable = t
.OrderBy(r =>
{ //if (r["ID"] is DBNull)
// return "";
//else
return r["ID"]; // ERROR
});
IEnumerable<DataRow> tue = sortedTable.Where(r => r["DAY"].Equals("TUE"));
IEnumerable<DataRow> wed = sortedTable.Where(r => r["DAY"].Equals("WED"));
AssignSections(tue);
AssignSections(wed);
Here is the query:
public void AssignSections(IEnumerable<DataRow> students)
{
IEnumerable<IEnumerable<DataRow>> query = from e in students.AsEnumerable()
orderby (e["SHORTSCHOOL"] as string).PadRight(30) + e["SEED"] as string
group e by new { DAY=e["DAY"], GRADE=e["GRADE"] } into g
orderby g.Key.GRADE as string
select g.AsEnumerable();
var queryList = query.ToList(); // ArgumentException during "WED" call
foreach (var grade in query)
foreach (var student in grade)
if (student["ID"] == DBNull.Value)
{
student["SECTION"] = "S";
student["ID"] = "ID1";
}
}
Assigning SECTION works, NO PROBLEM. Assigning ID causes query to look like:
query now appears invalid. Future uses of query also prove to be invalid (though the foreach finishes fine).
For what it's worth, grade is just fine, but students is also invalidated through the original table seems to be fine as well.
No magic here. It's a combination of LINQ query Deferred Execution and the usage of the DBNull, which cannot be compared to/from other types.
The deferred execution has been explained many times, so I'm not going to spend time on it. Shorty, the query is executed the only (but anytime) when enumerated. Enumerating means foreach, ToList etc. and technically speaking happens when the GetEnumerator of the enumerable (or the first MoveNext of the enumerator) is called.
All you need to remember from the above is that the IEnumerable<T> (or IQueryable<T>) returning LINQ queries are not executed (evaluated) at the time you define them, but every time you enumerate them (directly or indirectly). This should explain the "The answer surprisingly to me is that LINQ reorders code" part from your own answer. No, LINQ does not reorder the code, it's your code which is doing that by reevaluting the LINQ queries at certain points which are different from the place where you define your query variables. If you want to evaluate them just once at specific point, then do that by adding ToList, ToArray and similar methods which enumerate the query and store the result in some in memory collection and use that collection for further processing. It still be IEnumerable<T>, but further enumerations would enumerate the query result rather than reevaluate the query.
The main issue is the DBNull. From your explanations looks like initially all the ID values are DBNull, so the first query runs fine (DBNull knows how to compare to itself :). Once the source contains at least one value which is not DBNull, any further query that uses OrderBy that column with the default IComparer will fail.
It can easily be reproduced w/o data tables with the following simple code:
var data = new[]
{
new { Id = (object)DBNull.Value },
new { Id = (object)DBNull.Value }
};
var query = data.OrderBy(e => e.Id);
query.ToList(); // Success
data[1] = new { Id = (object)"whatever" };
query.ToList(); // Fail
showing the deferred query execution and reevaluation, or directly (to prove that the problem is not with editing):
new[]
{
new { Id = (object)DBNull.Value },
new { Id = (object)"whatever" }
}
.OrderBy(e => e.Id)
.ToList(); // Fail
The solution is to avoid DBNull at all. The easiest (and much better than as string or ToString()) with DataTable is to use DataRowExtensions.Field extension methods instead of object returning indexer, which besides providing strongly typed access to the columns also automatically handle DBNulls for you (converts them to null when you request string or nullable type), so you won't experience such issues.
It can be proved by changing your problematic code to
.OrderBy(r => r.Field<string>("ID"))
and the problem will be gone. I strongly recommend doing that for other column accessors as well.
The answer surprisingly to me is that LINQ reorders code. The context was this:
IEnumerable<DataRow> s = importTable.AsEnumerable();
IEnumerable<DataRow> t = s
.OrderBy(r => r["HALL"]);
IEnumerable<DataRow> sortedTable = t
.OrderBy(r =>
{ //if (r["ID"] is DBNull)
// return "";
//else
return r["ID"]; // ERROR
});
IEnumerable<DataRow> tue = sortedTable.Where(r => r["DAY"].Equals("TUE"));
IEnumerable<DataRow> wed = sortedTable.Where(r => r["DAY"].Equals("WED"));
AssignSections(tue);
AssignSections(wed);
The 3 commented lines indicate the fault. And what happened: sortedTable was partially initialized in order to feed the Where clause for initializing tue. But then the sortedTable was completed to initialize wed AFTER the call to assign wed appeared in the code, but just in time to use wed in the query constructed in AssignSections!
So the ERROR arose during AssignSections, when the code detoured to completing the initializing of sortedTable, and I could detect this by adding the 3 disabled lines and setting a breakpoint on the "return "";
Magic?
DBNull and null is not the same...
As your original error message says "Object must be of type string" (to be assigned to a string)
DBNull can't be cast to a string,it is a class...
You need to handle this case in your code.
See this link for a simple helper method:
Unable to cast object of type 'System.DBNull' to type 'System.String
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
DBNull dbNull = DBNull.Value;
Console.WriteLine(typeof(string).IsAssignableFrom(typeof(DBNull)));//False
Console.WriteLine(dbNull is string); //False
//Console.WriteLine((string)dbNull); // compile time error
//Console.WriteLine(dbNull as string); // compile time error
Console.ReadLine();
}
}
}
Also, make sure you read how "Lazy Loading"/"Deferred Execution" works on LINQ/IEnumerable.
You don't have to use IEnumerable all the time,especially if you are not sure how it works.
Related
I have a feeling I know that what the reason is for this behavior, but I don't know what the best way of resolving it will be.
I have built a LinqToSQL query:
public IEnumerable<AllConditionByCountry> GenerateConditions(int paramCountryId)
{
var AllConditionsByCountry =
(from cd in db.tblConditionDescriptions...
join...
join...
select new AllConditionByCountry
{
CountryID = cd.CountryID,
ConditionDescription = cd.ConditionDescription,
ConditionID = cd.ConditionID,
...
...
}).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();
return AllConditionsByCountry;
}
This query returns about 9500+ rows of data.
I'm calling this from my Controller like so:
svcGenerateConditions generateConditions = new svcGenerateConditions(db);
IEnumerable<AllConditionByCountry> AllConditionsByCountry;
AllConditionsByCountry = generateConditions.GenerateConditions(1);
Which then I'm looping through:
foreach (var record in AllConditionsByCountry)
{
...
...
...
This is where I think the issue is:
var rList = AllConditionsByCountry
.Where(x => x.ConditionID == conditionID)
.Select(x => x)
.AsEnumerable();
I'm doing an nested loop based off the data that I'm gathering from the above query (utilizing the original data I'm getting from AllConditionByCountry. I think this is where my issue lies. When it is doing the filter on the data, it SLOWS down greatly.
Basically this process writes out a bunch of files (.json, .html)
I've tested this at first using just ADO.Net and to run through all of these records it took about 4 seconds. Using EF (stored procedure or LinqToSql) it takes minutes.
Is there anything I should do with my types of lists that I'm using or is that just the price of using LinqToSql?
I've tried to return List<AllConditionByCountry>, IQueryable, IEnumerable from my GenerateConditions method. List took a very long time (similar to what I'm seeing now). IQueryable I got errors when I tried to do the 2nd filter (Query results cannot be enumerated more than once).
I have run this same Linq statement in LinqPad and it returns in less than a second.
I'm happy to add any additional information.
Please let me know.
Edit:
foreach (var record in AllConditionsByCountry)
{
...
...
...
var rList = AllConditionsByCountry
.Where(x => x.ConditionID == conditionID)
.Select(x => x)
.AsEnumerable();
conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
id = conditionDescriptionTypeID + "_" + count.ToString();
...
...
}
TL;DR: You're making 9895 queries against the database instead of one. You need to rewrite your query such that only one is executed. Look into how IEnumerable works for some hints into doing this.
Ah, yeah, that for loop is your problem.
foreach (var record in AllConditionsByCountry)
{
...
...
...
var rList = AllConditionsByCountry.Where(x => x.ConditionID == conditionID).Select(x => x).AsEnumerable();
conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
id = conditionDescriptionTypeID + "_" + count.ToString();
...
...
}
Linq-to-SQL works similarly to Linq in that it (loosely speaking) appends functions to a chain to be executed when the enumerable is iterated - for example,
Enumerable.FromResult(1).Select(x => throw new Exception());
This doesn't actually cause your code to crash because the enumerable is never iterated. Linq-to-SQL operates on a similar principle. So, when you define this:
var AllConditionsByCountry =
(from cd in db.tblConditionDescriptions...
join...
join...
select new AllConditionByCountry
{
CountryID = cd.CountryID,
ConditionDescription = cd.ConditionDescription,
ConditionID = cd.ConditionID,
...
...
}).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();
You're not executing anything against a database, you're just instructing C# to build a query that does this when it is iterated. That's why just declaring this query is fast.
Your problem comes when you get to your for loop. When you hit your for loop, you signal that you want to start iterating the AllConditionsByCountry iterator. This causes .NET to go off and execute the initial query, which takes time.
When you call AllConditionsByCountry.Where(x => x.ConditionID == conditionID) in the for loop, you're constructing another iterator that doesn't actually do anything. Presumably you actually use the result of rList within that loop, however, you're essentially constructing N queries to be executed against the database (where N is the size of AllConditionsByCountry).
This leads to a scenario where you are effectively executing approximately 9501 queries against the database - 1 for your initial query and then one query for each element within the original query. The drastic slowdown compared to ADO.NET is because you're probably making 9500 more queries than you were originally.
You ideally should change the code so that there is one and only one query executed against the database. You've a couple of options:
Rewrite the Linq-to-SQL query such that all of the legwork is done by the SQL database
Rewrite the Linq-to-SQL query so it looks like this
var conditions = AllConditionsByCountry.ToList();
foreach (var record in conditions)
{
var rList = conditions.Where(....);
}
Note that in that example I am searching conditions rather than AllConditionsByCountry - .ToList() will return a list that has already been iterated so you do not create any more database queries. This will still be slow (since you're doing O(N^2) over 9500 records), but it will still be faster than creating 9500 queries since it will all be done in memory.
Just rewrite the query in ADO.NET if you're more comfortable with raw SQL than Linq-to-SQL. There's nothing wrong with this.
I think I should point out what methods cause an IEnumerable to be iterated and what ones don't.
Any method named As* (such as AsEnumerable<T>()) do not cause the enumerable to be iterated. It's essentially a way of casting from one type to another.
Any method named To* (such as ToList<T>()) will cause the enumerable to be iterated. In the event of Linq-to-SQL this will also execute the database query. Any method that also results in you getting a value out of the enumerable will also cause iteration. You can use this to your advantage by creating a query and forcing iteration using ToList() and then searching that list - this will cause the comparisons to be done in memory, which is what I demo above
//Firstly: IEnumerable<> should be List<>, because you need to massage result later
public IEnumerable<AllConditionByCountry> GenerateConditions(int paramCountryId)
{
var AllConditionsByCountry =
(from cd in db.tblConditionDescriptions...
join...
join...
select new AllConditionByCountry
{
CountryID = cd.CountryID,
ConditionDescription = cd.ConditionDescription,
ConditionID = cd.ConditionID,
...
...
})
.OrderBy(x => x.CountryID)
.ToList() //return a list, so only 1 query is executed
//.AsEnumerable<AllConditionByCountry>();//it's useless code, anyway.
return AllConditionsByCountry;
}
about this part:
foreach (var record in AllConditionsByCountry) // you can use AllConditionsByCountry.ForEach(record=>{...});
{
...
//AllConditionsByCountry will not query db again, because it's a list, no long a query
var rList = AllConditionsByCountry.Where(x => x.ConditionID == conditionID);//.Select(x => x).AsEnumerable(); //no necessary to use AsXXX if compilation do not require it.
...
}
BTW,
you should have your result paged, no page will need 100+ result. 10K return is the issue itself.
GenerateConditions(int paramCountryId, int page = 0, int pagesize = 50)
it's weird that you have to use a sub-query, usually it means GenerateConditions did not return the data structure you need, you should change it to give right data, no more subquery
use compiled query to improve more: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities
we don't see your full query, but usually, it's right the part you should improve, especially when you have many conditions to filter and join and group... a little change could make all differences.
For instance
myEnumerable.Where(v => v != myDictionary["someKey"])
when this query is called is myDictionary["someKey"] statement executed (meaning that dictionary is queried for the key) or the result of myDictionary["someKey"]
is used after the first iteration?
The result of myDictionary["someKey"] will not be cached(*see edit below), it will be accessed on every item of myEnumerable. However, you can still cache it manually :
var someValue = myDictionary["someKey"];
myEnumerable.Where(v => v != someValue)
Also take note that, if you plan to iterate/access that IEnumerable multiple time, it is best to actualize it via ToList(). Or, the execution will be deferred every single time.
var query = myEnumerable.Where(v => v != myDictionary["someKey"]);
foreach (var item in query) { /* ... */}
foreach (var item in query) { /* ... */}
In the above example, the Where clause is executed twice.
EDIT: As #LucasTrzesniewski has pointed out, this is only stands true for LINQ-to-Objects. This is because the query is evaluated in memory. However, for LINQ-to-Entities, it gets a little bit different, as the query will be converted into SQL query and then executed in the database in order to avoid round trips.
Here's a really simple demo (and please, don't try this at home):
var myDictionary = new Dictionary<string,string>() { { "someKey", "someValue" } };
var myEnumerable = new List<string> { "someValue", "someOtherValue" };
var test = myEnumerable.Where(v => v == myDictionary["someKey"]);
foreach (var t in test)
{
Console.WriteLine(t);
myDictionary["someKey"] = "someOtherValue";
}
If myDictionary["someKey"] was only evaulated once, then changing the value of myDictionary["someKey"] wouldn't change anything. But if you run the code, you will see that it will echo both someValue and someOtherValue. If you comment out the line that changes the dictionary value, then you will only see someValue
As #Lucas Trzesniewski points out in the comments to the other answer, this applies to LINQ-to-objects. There are a number of important differences between LINQ-to-objects and LINQ-to-SQL.
The Lambda expression you supply to the Linq Where extension is simply a Func<> delegate. The method is executed for each item in the IEnumerable(of T), receiving the current item as a parameter. It doesn't do anything special other than that. Your code is somewhat similar similar to:
var myTempCollection = new List<MyClass>();
foreach(MyClass item in myEnumerable)
{
if (item != myDictionary["someKey"])
{
myTempCollection.Add(item);
}
}
var result = myTempCollection;
It depends on the QueryProvider implementation. For example, the ObjectQueryProvider used by Linq-to-objects will access it on every iteration. For Linq-to-entities, it will access it once and then send that value to the database server.
I'm migrating some stuff from one mysql server to a sql server but i can't figure out how to make this code work:
using (var context = new Context())
{
...
foreach (var item in collection)
{
IQueryable<entity> pages = from p in context.pages
where p.Serial == item.Key.ToString()
select p;
foreach (var page in pages)
{
DataManager.AddPageToDocument(page, item.Value);
}
}
Console.WriteLine("Done!");
Console.Read();
}
When it enters into the second foreach (var page in pages) it throws an exception saying:
LINQ to Entities does not recognize the method 'System.String
ToString()' method, and this method cannot be translated into a store
expression.
Anyone know why this happens?
Just save the string to a temp variable and then use that in your expression:
var strItem = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == strItem
select p;
The problem arises because ToString() isn't really executed, it is turned into a MethodGroup and then parsed and translated to SQL. Since there is no ToString() equivalent, the expression fails.
Note:
Make sure you also check out Alex's answer regarding the SqlFunctions helper class that was added later. In many cases it can eliminate the need for the temporary variable.
As others have answered, this breaks because .ToString fails to translate to relevant SQL on the way into the database.
However, Microsoft provides the SqlFunctions class that is a collection of methods that can be used in situations like this.
For this case, what you are looking for here is SqlFunctions.StringConvert:
from p in context.pages
where p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;
Good when the solution with temporary variables is not desirable for whatever reasons.
Similar to SqlFunctions you also have the EntityFunctions (with EF6 obsoleted by DbFunctions) that provides a different set of functions that also are data source agnostic (not limited to e.g. SQL).
The problem is that you are calling ToString in a LINQ to Entities query. That means the parser is trying to convert the ToString call into its equivalent SQL (which isn't possible...hence the exception).
All you have to do is move the ToString call to a separate line:
var keyString = item.Key.ToString();
var pages = from p in context.entities
where p.Serial == keyString
select p;
Cast table to Enumerable, then you call LINQ methods with using ToString() method inside:
var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)
But be careful, when you calling AsEnumerable or ToList methods because you will request all data from all entity before this method. In my case above I read all table_name rows by one request.
Had a similar problem.
Solved it by calling ToList() on the entity collection and querying the list.
If the collection is small this is an option.
IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())
Hope this helps.
Upgrading to Entity Framework Version 6.2.0 worked for me.
I was previously on Version 6.0.0.
Hope this helps,
Change it like this and it should work:
var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == key
select p;
The reason why the exception is not thrown in the line the LINQ query is declared but in the line of the foreach is the deferred execution feature, i.e. the LINQ query is not executed until you try to access the result. And this happens in the foreach and not earlier.
If you really want to type ToString inside your query, you could write an expression tree visitor that rewrites the call to ToString with a call to the appropriate StringConvert function:
using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;
namespace ToStringRewriting {
class ToStringRewriter : ExpressionVisitor {
static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
.Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));
protected override Expression VisitMethodCall(MethodCallExpression node) {
var method = node.Method;
if (method.Name=="ToString") {
if (node.Object.GetType() == typeof(string)) { return node.Object; }
node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
}
return base.VisitMethodCall(node);
}
}
class Person {
string Name { get; set; }
long SocialSecurityNumber { get; set; }
}
class Program {
void Main() {
Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
var rewriter = new ToStringRewriter();
var finalExpression = rewriter.Visit(expr);
var dcx = new MyDataContext();
var query = dcx.Persons.Where(finalExpression);
}
}
}
In MVC, assume you are searching record(s) based on your requirement or information.
It is working properly.
[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{
EmployeeContext employeeContext = new EmployeeContext();
string searchby=formcollection["SearchBy"];
string value=formcollection["Value"];
if (formcollection["SearchBy"] == "Gender")
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
return View("Index", emplist);
}
else
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
return View("Index", emplist);
}
}
I got the same error in this case:
var result = Db.SystemLog
.Where(log =>
eventTypeValues.Contains(log.EventType)
&& (
search.Contains(log.Id.ToString())
|| log.Message.Contains(search)
|| log.PayLoad.Contains(search)
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
)
)
.OrderByDescending(log => log.Id)
.Select(r => r);
After spending way too much time debugging, I figured out that error appeared in the logic expression.
The first line search.Contains(log.Id.ToString()) does work fine, but the last line that deals with a DateTime object made it fail miserably:
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
Remove the problematic line and problem solved.
I do not fully understand why, but it seems as ToString() is a LINQ expression for strings, but not for Entities. LINQ for Entities deals with database queries like SQL, and SQL has no notion of ToString(). As such, we can not throw ToString() into a .Where() clause.
But how then does the first line work? Instead of ToString(), SQL have CAST and CONVERT, so my best guess so far is that linq for entities uses that in some simple cases. DateTime objects are not always found to be so simple...
My problem was that I had a 'text' data type for this column (due to a migration from sqlite).
Solution: just change the data type to 'nvarchar()' and regenerate the table.
Then Linq accepts the string comparison.
I am working on retiring Telerik Open Access and replacing it with Entity Framework 4.0. I came across same issue that telerik:GridBoundColumn filtering stopped working.
I find out that its not working only on System.String DataTypes. So I found this thread and solved it by just using .List() at the end of my Linq query as follows:
var x = (from y in db.Tables
orderby y.ColumnId descending
select new
{
y.FileName,
y.FileSource,
y.FileType,
FileDepartment = "Claims"
}).ToList();
Just turn the LINQ to Entity query into a LINQ to Objects query (e.g. call ToArray) anytime you need to use a method call in your LINQ query.
I have a function like that :
public int? calculateContractPrice(int? comid)
{
int? sum = 0;
var q = from i in dbconnect.tblMaterialGroups
where i.tenderId == _tenderId
select i.id;
foreach (int i in q )
{
var q2 = from g in dbconnect.tblMaterialTenderAnnouncePrices
where g.MaterialGroupId == i && g.companyId == comid
select g;
sum = q2.First().amount*q2.First().price + q2.First().amount*q2.First().PriceForElse + sum;
}
return sum ;
}
When i try to execute this :
List<presentationcontract> q = (from i in dbconnect.tblContracts
where i.tender == _tenderId
select new presentationcontract()
{
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
}).ToList();
Tax is string .after executing i got this error :
couldn't translate expression calculateContractPrice(i.companyId)*(6/100),invoke(value(system.Func1[system.nullable1[system.Int32]]))).ToString() into SQL and could not treat it as a local expression
Your edit makes clear the issue. You're trying to do
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
in a sql statement but calculateContractPrice is in c#! To understand what's going on you really need to understand a bit how LINQ works.
First of all, stop using the silly sql-style syntax for LINQ. It is less powerful than the lambda syntax and hides what is going on under the hood in a way that makes it hard to understand.
Second consider a LINQ statement
users.Where(u => u.Name == "George").ToList();
where users is IEnumerable<User>. What happens here is that the lambda part is of type Func<User, bool> and gets compiled to a method that gets run against every single instance of User.
Now consider this LINQ statement
db.Users.Where(u => u.Name == "George").ToList();
where db.Users is IQueryable<T>. This looks the same but what happens is VERY different. What happens is that lambda is actually of type Expression<Func<User, bool>> this doesn't get compiled to a method, instead it gets compiled into something called an expression tree. This gets passed to the LINQ provider (in your case Entity Framework I'm guessing) which examines it and converts that into a SQL statement
SELECT Id, Name, Address FROM users WHERE Name = 'George'
What is happening in your case is that it sees the call to calculateContractPrice and simply has no way of converting that to SQL.
What you should therefore do is ensure the query runs first, then use the IEnumerable<T> form of LINQ that runs in c# to call your method.
var contracts = dbconnect.tblContracts.Where(i => i.tender == _tenderId)
.ToList() //Query executes here, now you have IEnumerable<T>
.Select(i => new PresentationContract {
Tax = ...
}).ToList(); //this ToList is only necessary if you want to prevent multiple iteration
You will want to solve all the other problems everyone else pointed out as well of course.
A few other notes - you will want to read up on .Net naming conventions. Usually, anything public,protected, or internal (classes, fields, properties, etc.) is recommended to be PascalCase. Also you probably want to move the division portion into the PresentationContract class. This class that has a Tax property should probably be the one that knows how to generate it.
Try this:
int? ret = calculateContractPrice(i.companyId);
if(ret.HasValue)
{
tax =(ret.Value*(6/100)).ToString();
}
You should make sure that the function indeed returned a value and then you use that integer value in calculation.
I have a gridview, the datasource of which is the following function:
public static List<Train> GetTrainsByIDs(int [] ids) {
using (var context = new MyEntities())
{
return ids.Select(x => context.Trains.Single(y => y.TrainID ==x)).AsQueryable().Include(x=>x.Station).ToList();
}
}
The grid view has an ItemTemplate of <%# Eval("Station.Name") %>.
This causes the error The ObjectContext instance has been disposed and can no longer be used for operations that require a connection despite the fact that I used the include method.
When I change the function to
public static List<Train> GetTrainsByIDs(int [] ids) {
using (var context = new MyEntities())
{
return context.Trains.Where(x => ids.Contains(x.TrainID)).Include(x=>x.Station).ToList();
}
}
it works fine, but then they come out in the wrong order, and also if I have 2 ids the same I would like 2 identical trains in the list.
Is there anything I can do other than create a new viewmodel? Thank you for any help
As for the first query: that's deferred execution.You created an IEnumerable of Trains, noticed that it did not have the Include method, so cast it to IQueryable, added the Include and added the ToList() to prevent lazy loading.
But As per MSDN on DbExtensions.Include:
This extension method calls the Include(String) method of the IQueryable source object, if such a method exists. If the source IQueryable does not have a matching method, then this method does nothing.
(emphasis mine)
The result of the select is an IEnumerable converted to IQueryable, but now implemented by EnumerableQuery which does not implement Include. And nothing happens.
Now the data enters the grid which tries to display the station, which triggers lazy loading while the context is gone.
Apart from that, this design has another flaw: it fires a query for each id separately.
So the second query is much better. It is one query, including the Stations. But now the order is dictated by the order the database pleases to return. You could use Concat to solve this:
IQueryable<Train> qbase = context.Trains.Include(x=>x.Station);
IQueryable<Train> q = null;
foreach (var id in ids)
{
var id1 = id; // Prevent modified closure.
if (q == null)
q = qbase.Where(t => t.Id == id1);
else
q = q.Concat(qbase.Where (t => t.Id == id1));
}
The generated query is not very elegant (to say the least) but after all it is one query as opposed to many.
After reading #Gert Arnold's answer, and getting the idea of doing it in 2 stages, I managed very simply using the first query like this:
using (context = new MyEntities())
{
var trns = context.Trains.Include(x => x.Station);
return ids.Select(x => trns.Single(y => y.TrainID == x)).ToList();
}