Translate sql in linq - asp.net

Hello I have to write in linq the sql query below:
Declare #OrgID int
Declare #OrgFinalID int
Set #OrgID = 91702 ---91703, 91702, 83279
select #OrgFinalID =
case
when ParentOrganisationId is null then ItemID
else ParentOrganisationId
end
from Organisations
where ItemID = #OrgID
I tried to write this but I am not on the right way, sorry but I am new with LINQ:
var OrgID=91207;
var OrgFinalID = from o in context.Organisations
where o.ItemID == OrgID
select new
{
o.ParentOrganisationId == null ? o.ItemID : o.ParentOrganisationId,
}
I have to put, with the LINQ expression, the value inside the variable OrgFinalID.

Looks like you are expecting this to be just a single number? Than you can call .Single() on your query, that basically returns the value itself:
var OrgFinalID = (from o in context.Organisations
where o.ItemID == OrgID
select new
{
ID = o.ParentOrganisationId == null ? o.ItemID : Convert.ToInt32(o.ParentOrganisationId),
}).Single().ID;
Also note call to Convert.ToInt32 which is supported by Linq to SQL and should help you avoid type casting problem.
Other options:
SingleOrDefault - if there could be single result, or no result at all (returns null in this case)
First - if you expect one or more results from the query
FirstOrDefault - similar to SingleOrDefault , returns null if no results came from the query

Related

Linq. Anonymous type error when joining to multiple tables

Im trying to return an IQueryable based on my model.
But I need to join to the same lookup table twice. Then return the query variable to the gridview.
public IQueryable<Benchmark> GetBenchMarks([QueryString("hydrant")] string hydrant,
[QueryString("revdate")] string revdate, [QueryString("street")] string street,
[QueryString("quadrant")] string quadrant, [QueryString("desc")] string desc) {
IQueryable<Benchmark> query = from p in _db.Benchmarks
join s in _db.Streets on p.Street1Number equals s.Id
join s2 in _db.Streets on p.Street2Number equals s2.Id
select new {
Street1Name = s.StreetName,
p.OrderNumber,
p.HydrantNumber,
Street2Name = s2.StreetName,
p.RevisionDate,
p.Quadrant,
p.Description,
p.Street1Number
};
}
So there is a red squiggle line on the 2nd join to s2. And the following error.
Error 5 Cannot implicitly convert type
'System.Linq.IQueryable<AnonymousType#1>' to
'System.Linq.IQueryable<Benchmarks.Model.Benchmark>'. An explicit
conversion exists (are you missing a
cast?) C:\Projects\Benchmarks\Benchmarks\Benchmarks_Home.aspx.cs 63 25 Benchmarks
Since you end your query with select new {...}, you are creating an anonymous object for each result. Instead, use select p, and each result will be a Benchmark.
However, it looks like returning a Benchmark is not what you want. In this case, you would want to change query to be of type IQueryable or IQueryable<dynamic> (and probably change the return type of the GetBenchMarks function as well, unless it does return IQueryable<Benchmark>!).
A second (potentially better) alternative would be to create a class to represent this anonymous type, and use that.
The result of your query is IEnumerable of anonymous objects, thus it cannot be converted to Benchmark.
If you want to set some additional properties (Street1Name - that are evidently not mapped on DB) from joined relations you can do:
IQueryable<Benchmark> query = from p in _db.Benchmarks
join s in _db.Streets on p.Street1Number equals s.Id
join s2 in _db.Streets on p.Street2Number equals s2.Id
select new {
....
};
var ex = query.ToList();
var result = new List<Benchmark>();
foreach(bn in ex){
result.Add(new Benchmark{ OrderNumber = bn.OrderNumber .... });
}
// return result.AsQueryable();
// but now it losts the point to return it as queryable, because the query was already executed so I would simply reurn that list
return result;
Another option is to make new class representing the object from the query and return it from the method like:
... select new LoadedBenchmark { Street1Name = s.StreetName ....}

LinqtoSql using .first() with asp.net gridview

I am using entity framework with the following linq query
IQueryable<Order_Details> query = (from ord in ctx1.Order_Details
where ord.OrderID == 1
select ord).ToArray();
gv1.DataSource = query;
gv1.DataBind();
I get a result ok, return a row with orderId of 1
When I use the following,
var query = (from ord in ctx1.Order_Details
where ord.OrderID == 1
select ord).First() as IQueryable<Order_Details>;
gv1.DataSource = query;
gv1.DataBind();
I don't recive any results in the gridview
First() returns the Order_Detail record itself, not a collection/array, and therefore is not IQueryable; so using as IQueryable<Order_Details> will return null because it can't be cast. Casting to IQueryable<> can only work for an enumerable of some sort.
If you have to use First, bind to the gridview like this:
var query = (from ord in ctx1.Order_Details
where ord.OrderID == 1
select ord).First();
gv1.DataSource = new[] { query };
gv1.DataBind();
You're trying to cast a single Order_Details object to IQueryable<Order_Details>
Since that's not a valid cast, null is being returned, and your grid is not showing any results.
Your GridView is expecting an IEnumerable, so your original code was correct. Why did you try to change it?

LINQ - 'Could not translate expression' with previously used and proven query condition

I am fairly new to LINQ and can't get my head around some inconsistency in behaviour. Any knowledgeable input would be much appreciated. I see similar issues on SO and elsewhere but they don't seem to help.
I have a very simple setup - a company table and an addresses table. Each company can have 0 or more addresses, and if > 0 one must be specified as the main address. I'm trying to handle the cases where there are 0 addresses, using an outer join and altering the select statement accordingly.
Please note I'm currently binding the output straight to a GridView so I would like to keep all processing within the query.
The following DOES work
IQueryable query =
from comp in context.Companies
join addr in context.Addresses on comp.CompanyID equals addr.CompanyID into outer // outer join companies to addresses table to include companies with no address
from addr in outer.DefaultIfEmpty()
where (addr.IsMain == null ? true : addr.IsMain) == true // if a company has no address ensure it is not ruled out by the IsMain condition - default to true if null
select new {
comp.CompanyID,
comp.Name,
AddressID = (addr.AddressID == null ? -1 : addr.AddressID), // use -1 to represent a company that has no addresses
MainAddress = String.Format("{0}, {1}, {2} {3} ({4})", addr.Address1, addr.City, addr.Region, addr.PostalCode, addr.Country)
};
but this displays an empty address in the GridView as ", , ()"
So I updated the MainAddress field to be
MainAddress = (addr.AddressID == null ? "" : String.Format("{0}, {1}, {2} {3} ({4})", addr.Address1, addr.City, addr.Region, addr.PostalCode, addr.Country))
and now I'm getting the Could not translate expression error and a bunch of spewey auto-generated code in the error which means very little to me.
The condition I added to MainAddress is no different to the working condition on AddressID, so can anybody tell me what's going on here?
Any help is greatly appreciated.
The error you are getting is telling you that LinqToSql cannot translate your null check and then string.Format expression into SQL. If you look at the SQL your first query is generating (using either LinqPad or SQL Profiler), you'll see something like:
SELECT [t0].[CompanyID], [t0].[Name],
(CASE
WHEN [t1].[AddressID] IS NULL THEN #p0
ELSE [t1].[AddressID]
END) AS [AddressID],
[t1].[Address1] AS [value],
[t1].[City] AS [value2],
[t1].[Region] AS [value3],
[t1].[PostalCode] AS [value4],
[t1].[Country] AS [value5]
FROM [Company] AS [t0]
LEFT OUTER JOIN [Address] AS [t1] ON [t0].[CompanyID] = [t1].[CompanyID]
WHERE ([t1].[IsMain] IS NULL) OR ([t1].[IsMain] = 1)
For your AddressID field, you can see that it uses a CASE-WHEN to handle the condition when AddressID is null. When you add a CASE-WHEN for MainAddress, it's trying to do the same thing for that field, but there is no SQL equivalent to string.Format it can use for the ELSE clause, so it blows up.
An easy way around this problem is to use a method to format the string. By calling a private method, LinqToSql won't try to translate the string.Format to SQL, and will instead return all of the fields necessary to populate the Address object. The method can then take care of the formatting.
For example:
LINQ:
....
select new {
comp.CompanyID,
comp.Name,
AddressID = (addr.AddressID == null ? -1 : addr.AddressID),
MainAddress = FormatAddress(addr)
};
Method:
private static string FormatAddress(Address addr)
{
return (addr == null ? "" :
string.Format("{0}, {1}, {2} {3} ({4})",
addr.Address1, addr.City,
addr.Region, addr.PostalCode, addr.Country));
}

Linq and SQL query comparison

I have this SQL query:
SELECT Sum(ABS([Minimum Installment])) AS SumOfMonthlyPayments FROM tblAccount
INNER JOIN tblAccountOwner ON tblAccount.[Creditor Registry ID] = tblAccountOwner.
[Creditor Registry ID] AND tblAccount.[Account No] = tblAccountOwner.[Account No]
WHERE (tblAccountOwner.[Account Owner Registry ID] = 731752693037116688)
AND (tblAccount.[Account Type] NOT IN
('CA00', 'CA01', 'CA03', 'CA04', 'CA02', 'PA00', 'PA01', 'PA02', 'PA03', 'PA04'))
AND (DATEDIFF(mm, tblAccount.[State Change Date], GETDATE()) <=
4 OR tblAccount.[State Change Date] IS NULL)
AND ((tblAccount.[Account Type] IN ('CL10','CL11','PL10','PL11')) OR
CONTAINS(tblAccount.[Account Type], 'Mortgage')) AND (tblAccount.[Account Status ID] <> 999)
I have created a Linq query:
var ownerRegistryId = 731752693037116688;
var excludeTypes = new[]
{
"CA00", "CA01", "CA03", "CA04", "CA02",
"PA00", "PA01", "PA02", "PA03", "PA04"
};
var maxStateChangeMonth = 4;
var excludeStatusId = 999;
var includeMortgage = new[] { "CL10", "CL11", "PL10", "PL11" };
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment).ToList()
.Sum(minimumInstallment =>
Math.Abs((decimal)(minimumInstallment)));
return sum;
Are they equal/same ? I dont have records in db so I cant confirm if they are equal. In SQL there are brackets() but in Linq I didnt use them so is it ok?
Please suggest.
It is not possible for us to say anything about this, because you didn't show us the DBML. The actual definition of the mapping between the model and the database is important to be able to see how this executes.
But before you add the DBML to your question: we are not here to do your work, so here are two tips to find out whether they are equal or not:
Insert data in your database and run the queries.
Use a SQL profiler and see what query is executed by your LINQ provider under the covers.
If you have anything more specific to ask, we will be very willing to help.
The brackets will be generated by LINQ provider, if necessary.
The simplest way to check if the LINQ query is equal to the initial SQL query is to log it like #Atanas Korchev suggested.
If you are using Entity Framework, however, there is no Log property, but you can try to convert your query to an ObjectQuery, and call the ToTraceString method then:
string sqlQuery = (sum as ObjectQuery).ToTraceString();
UPD. The ToTraceString method needs an ObjectQuery instance for tracing, and the ToList() call already performs materialization, so there is nothing to trace. Here is the updated code:
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment);
string sqlQuery = (sum as ObjectQuery).ToTraceString();
Please note that this code will not perform the actual query, it is usable for testing purposes only.
Check out this article if you are interested in ready-for-production logging implementation.
There can be a performance difference:
The SQL query returns a single number (SELECT Sum...) directly from the database server to the client which executes the query.
In your LINQ query you have a greedy operator (.ToList()) in between:
var sum = (...
...
select account.MinimumInstallment).ToList()
.Sum(minimumInstallment =>
Math.Abs((decimal)(minimumInstallment)));
That means that the query on the SQL server does not contain the .Sum operation. The query returns a (potentially long?) list of MinimumInstallments. Then the .Sum operation is performed in memory on the client.
So effectively you switch from LINQ to Entities to LINQ to Objects after .ToList().
BTW: Can you check the last proposal in your previous question here which would avoid .ToList() on this query (if the proposal should work) and would therefore be closer to the SQL statement.

Cannot implicitly convert type 'System.Data.Linq.ISingleResult<CustomeProcedureName> to 'int'

Sorry for this simple question .
I have a Stored Procedure that return an int value , I'm trying to call this sp from my asp.net linq to sql project .
int currentRating = db.sproc_GetAverageByPageId(pageId);
But i get this error :
Cannot implicitly convert type `'System.Data.Linq.ISingleResult<PsychoDataLayer.sproc_GetAverageByPageId> to 'int' .`
Edit 1
The solution that friends implied didn't work . All the time it return 0
For more information i put my stored procedure here :
ALTER procedure [dbo].[sproc_GetAverageByPageId](
#PageId int )
as
select (select sum(score) from votes where pageId = #PageId)/(select count(*) from votes where pageId=#PageId)
You should inspect the ReturnValue property.
Perhaps the following works better?
int currentRating = (int)db.sproc_GetAverageByPageId(pageId).ReturnValue;
Update: since your stored proc returns a resultset instead of using a return statement the actual data will be available as an element in the enumerable returned by db.sproc_GetAverageByPageId(pageId). If you inspect the ISingleResult<T> type, you'll see that it inherits IEnumerable<T> which indicates that you can enumerate the object to get to the data, each element being of type T.
Since the sproc does a SELECT SUM(*) ... we can count on the resultset to always contain one row. Thus, the following code will give you the first (and only) element in the collection:
var sumRow = db.sproc_GetAverageByPageId(pageId).Single();
Now, the type of sumRow will be T from the interface definition, which in your case is PsychoDataLayer.sproc_GetAverageByPageId. This type hopefully contains a property that contains the actual value you are after.
Perhaps you can share with us the layout of the PsychoDataLayer.sproc_GetAverageByPageId type?
Looks like you're actually after the ReturnValue. You may need to cast it to System.Data.Linq.ISingleResult if it isn't already, then cast ReturnValueto int.
This is actually returning an ISingleResult
int currentRating = (int) db.sproc_GetAverageByPageId(pageId).ReturnValue;
Change your sp to :
ALTER procedure [dbo].[sproc_GetAverageByPageId](
#PageId int )
as
return (select sum(score) from votes where pageId = #PageId)/(select count(*) from votes where pageId=#PageId)
one more thing you can do:
ALTER procedure [dbo].[sproc_GetAverageByPageId](#PageId int ) as
select (select sum(score) from votes where pageId = #PageId)/(SELECT * FROM votes where pageId=#PageId)
WRITE >>
"select * From"<< instead of "select Count(*)"
select (select sum(score) from votes where pageId = #PageId)/(SELECT * FROM votes where pageId=#PageId)
and after that:
int currentRating = (int)db.sproc_GetAverageByPageId(pageId).count();

Resources