I have a similar problem as described in
EF can't infer return schema from Stored Procedure selecting from a #temp table
and I have created my stored procedure solution based on the solution described above BUT I am still getting a similar EF error and I really don't know why or understand how I can fix it.
A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
My specific error:
The data reader is incompatible with the specified 'TestModel.sp_SoInfoDocs_Result'. A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
My stored procedure:
ALTER PROCEDURE [dbo].[sp_SoInfoDocs]
#searchText nvarchar(200),
#PageNumber int,
#PageSize int
AS
BEGIN
IF 1 = 2
BEGIN
SELECT
cast(null as int ) as rowNum
,cast(null as text) as serverName
,cast(null as text) as jobName
,cast(null as DATETIME) as oDate
,cast(null as int) as runCount
,cast(null as nvarchar(10)) as orderID
,cast(null as text) as applicationName
,cast(null as text) as memberName
,cast(null as text) as nodeID
,cast(null as nvarchar(10)) as endStatus
,cast(null as int) as returnCode
,cast(null as DATETIME) as startTime
,cast(null as DATETIME) as endTime
,cast(null as nvarchar(50)) as status
,cast(null as text) as owner
,cast(null as bit) as existsNote
WHERE
1 = 2
END
DECLARE #LowerLimit int;
SET #LowerLimit = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit int;
SET #UpperLimit = #PageNumber * #PageSize;
PRINT CAST (#LowerLimit as varchar)
PRINT CAST (#UpperLimit as varchar)
SELECT ROW_NUMBER() over (order by Expr1) as rowNum, *
into #temp
from (
SELECT dbo.SOInfo.jobName, dbo.SOInfo.nodeID, dbo.SOInfo.nodeGroup, dbo.SOInfo.endStatus, dbo.SOInfo.returnCode, dbo.SOInfo.startTime, dbo.SOInfo.endTime,
dbo.SOInfo.oDate, dbo.SOInfo.orderID, dbo.SOInfo.status, dbo.SOInfo.runCount, dbo.SOInfo.owner, dbo.SOInfo.cyclic, dbo.SOInfo.soInfoID, dbo.SOInfo.docInfoID,
dbo.SOInfo.existsNote, dbo.SOInfo.noSysout, dbo.serverInfo.serverName, dbo.Groups.label AS applicationName, Groups_1.label AS memberName,
Groups_2.label AS groupName, Groups_3.label AS scheduleTableName, dbo.SOInfo.serverInfoID, dbo.SOInfo.applicationID, dbo.SOInfo.groupID,
dbo.SOInfo.memberID, dbo.SOInfo.scheduleTableID, dbo.docFile.docFileID, dbo.docInfo.docInfoID AS Expr1, dbo.docFile.docFileObject
FROM dbo.SOInfo INNER JOIN
dbo.serverInfo ON dbo.SOInfo.serverInfoID = dbo.serverInfo.serverInfoID INNER JOIN
dbo.docInfo ON dbo.SOInfo.docInfoID = dbo.docInfo.docInfoID INNER JOIN
dbo.docFile ON dbo.docInfo.docFileID = dbo.docFile.docFileID LEFT OUTER JOIN
dbo.Groups AS Groups_3 ON dbo.SOInfo.scheduleTableID = Groups_3.ID LEFT OUTER JOIN
dbo.Groups AS Groups_1 ON dbo.SOInfo.memberID = Groups_1.ID LEFT OUTER JOIN
dbo.Groups AS Groups_2 ON dbo.SOInfo.groupID = Groups_2.ID LEFT OUTER JOIN
dbo.Groups ON dbo.SOInfo.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
) tbl
SELECT Count(1) FROM #temp
SELECT rowNum, serverName, jobName ,oDate,runCount,orderID,applicationName,memberName,nodeID, endStatus, returnCode,startTime,endTime,status,owner,existsNote
FROM #temp WHERE rowNum > #LowerLimit AND rowNum <= #UpperLimit
END
My overall goals are:
search through clustered indexed table (docInfo) and find all rows that contain a specific search string value
at the same time capture metadata from other tables associated with each docInfo object
The results of actions (1) and (2) above are written to a #temp table to which I then add a rowNum column to enable me to introduce paging i.e.
introduce paging for the number of metadata results that can be returned at any one time, based on supplied PageNumber and PageSize variables.
What does work
I am able to successfully create the stored procedure.
Within SSMS I am able to successfully execute the stored procedure and it delivers the results I expect, here's an example
Within EF I have been able to update and import the stored procedure by updating from database
Within EF I am then able to see the Function Imports and can see the Mapping
Within ED I am then able to see the generated complex types
I use the following code to call the process
using (TestEntities context = new TestEntities())
{
List<sp_SoInfoDocs_Result> lst = context.sp_SoInfoDocs(searchText, 1, 10).ToList();
}
I compile and run my solution and get the following error from EF
'System.Data.Entity.Core.EntityCommandExecutionException' occurred in EntityFramework.SqlServer.dll
Additional information: The data reader is incompatible with the specified 'SysviewModel.sp_SoInfoDocs_Result'. A member of the type, 'rowNum', does not have a corresponding column in the data reader with the same name.
I am very much a novice / basic user when it comes to both SSMS / SQL and EF, this has stretched me as far as I understand / can go and I really don't know where to turn to next in order to resolve this problem.
I've searched extensively through SO and can see others who have had similar problems and have tried the solutions suggested but nothing seems to work for me.
I really would be very very grateful to anyone who could help me understand
what is it that is wrong / I've done wrong?
is there a better approach to achieve what I need?
ideas as to how I can fix this.
Thanks in advance
The solution I have found is to use SQL temporary variables in stead of using temporary tables which then enables me to expose the table columns via my final SQL Select statement and then consume them as meta data in EF using the "Add Function Imports" function.
Here's an example of my successfully working sp.
USE [TestDB]
GO
/****** Object: StoredProcedure [dbo].[sp_SoInfoDocs_Archive] Script Date: 09/07/2015 10:35:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_SoInfoDocs_Archive]
#searchText nvarchar(200),
#PageNumber int,
#PageSize int,
#out int = 0 output
AS
BEGIN
DECLARE #LowerLimit int;
SET #LowerLimit = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit int;
SET #UpperLimit = #PageNumber * #PageSize;
-- Create temporary column variables
Declare #temp TABLE
(
rowNum INT,
jobName text,
nodeID nvarchar(50),
nodeGroup text,
endStatus nvarchar(10),
returnCode int,
startTime datetime,
endTime datetime,
oDate smalldatetime,
orderID nvarchar(10),
status nvarchar(50),
runCount int,
owner text,
cyclic text,
soInfoID int,
docInfoID int,
existsNote bit,
noSysout bit,
serverName varchar(256),
applicationName nvarchar(255),
memberName nvarchar(255),
groupName nvarchar(255),
scheduleTableName nvarchar(255),
serverInfoID int,
applicationID int,
groupID int,
memberID int,
scheduleTableID int,
docFileID int,
Expr1 int,
docFileObject varbinary(MAX)
)
INSERT INTO #temp
SELECT ROW_NUMBER() over (order by Expr1) as rowNum, *
from (
SELECT dbo.SOInfoArchive.jobName,
dbo.SOInfoArchive.nodeID,
dbo.SOInfoArchive.nodeGroup,
dbo.SOInfoArchive.endStatus,
dbo.SOInfoArchive.returnCode,
dbo.SOInfoArchive.startTime,
dbo.SOInfoArchive.endTime,
dbo.SOInfoArchive.oDate,
dbo.SOInfoArchive.orderID,
dbo.SOInfoArchive.status,
dbo.SOInfoArchive.runCount,
dbo.SOInfoArchive.owner,
dbo.SOInfoArchive.cyclic,
dbo.SOInfoArchive.soInfoID,
dbo.SOInfoArchive.docInfoID,
dbo.SOInfoArchive.existsNote,
dbo.SOInfoArchive.noSysout,
dbo.serverInfo.serverName,
dbo.Groups.label AS applicationName,
Groups_1.label AS memberName,
Groups_2.label AS groupName,
Groups_3.label AS scheduleTableName,
dbo.SOInfoArchive.serverInfoID,
dbo.SOInfoArchive.applicationID,
dbo.SOInfoArchive.groupID,
dbo.SOInfoArchive.memberID,
dbo.SOInfoArchive.scheduleTableID,
dbo.docFile.docFileID,
dbo.docInfo.docInfoID AS Expr1,
dbo.docFile.docFileObject
FROM dbo.SOInfoArchive INNER JOIN
dbo.serverInfo ON dbo.SOInfoArchive.serverInfoID = dbo.serverInfo.serverInfoID INNER JOIN
dbo.docInfo ON dbo.SOInfoArchive.docInfoID = dbo.docInfo.docInfoID INNER JOIN
dbo.docFile ON dbo.docInfo.docFileID = dbo.docFile.docFileID LEFT OUTER JOIN
dbo.Groups AS Groups_3 ON dbo.SOInfoArchive.scheduleTableID = Groups_3.ID LEFT OUTER JOIN
dbo.Groups AS Groups_1 ON dbo.SOInfoArchive.memberID = Groups_1.ID LEFT OUTER JOIN
dbo.Groups AS Groups_2 ON dbo.SOInfoArchive.groupID = Groups_2.ID LEFT OUTER JOIN
dbo.Groups ON dbo.SOInfoArchive.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
) tbl
-- Select enables me to consume the following columns as meta data in EF
SELECT rowNum,
serverName,
jobName ,
oDate,
runCount,
orderID,
applicationName,
memberName,
nodeID,
endStatus,
returnCode,
startTime,
endTime,
status,
owner,
existsNote,
docFileID
FROM #temp WHERE rowNum > #LowerLimit AND rowNum <= #UpperLimit
END
So to recap, I can now
1) Import the stored procedure into my EDMX.
2) The "Add Function Import" successfully creates
a) sp_SoInfoDocs Function Imports
b) sp_SoInfoDocs Complex Types
I can now successfully call my stored procedure as follows
using (TestEntities context = new TestEntities())
{
string searchText = "rem";
ObjectParameter total = new ObjectParameter("out",typeof(int));
List<sp_SoInfoDocs_Result> lst = context.sp_SoInfoDocs(searchText, 1, 10, total).ToList();
foreach (var item in lst)
{
Console.WriteLine(item.jobName + " " + item.oDate + " " + item.serverName + " " + item.startTime + " " + item.endTime);
}
Console.ReadLine();
}
And an example of the results returned.
I am now successfully using the basis of this process to import and display the metadata in a dynamically created HTML table in my View.
If anyone else is experiencing similar problems and I have neglected to explain fully why I adopted this solution and how I made it work ~ then please feel free to P.M. me and I'll do my best to explain.
Why to use temporary table or table variable at all. Table variables has many performance drawbacks like:
Table variables does not have distribution statistics, they will not
trigger recompiles. Therefore, in many cases, the optimizer will build
a query plan on the assumption that the table variable has no rows.
For this reason, you should be cautious about using a table variable
if you expect a larger number of rows (greater than 100). Temp tables
may be a better solution in this case. Alternatively, for queries that
join the table variable with other tables, use the RECOMPILE hint,
which will cause the optimizer to use the correct cardinatlity for the
table variable.
table variables are not supported in the SQL Server optimizer's
cost-based reasoning model. Therefore, they should not be used when
cost-based choices are required to achieve an efficient query plan.
Temporary tables are preferred when cost-based choices are required.
This typically includes queries with joins, parallelism decisions, and
index selection choices.
Queries that modify table variables do not generate parallel query
execution plans. Performance can be affected when very large table
variables, or table variables in complex queries, are modified. In
these situations, consider using temporary tables instead. For more
information, see CREATE TABLE (Transact-SQL). Queries that read table
variables without modifying them can still be parallelized.
Use simple CTE:
ALTER PROCEDURE [dbo].[sp_SoInfoDocs_Archive]
#searchText NVARCHAR(200),
#PageNumber INT,
#PageSize INT,
#out INT = 0 OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #LowerLimit INT = (#PageNumber - 1) * #PageSize;
DECLARE #UpperLimit INT = #PageNumber * #PageSize;
;WITH cte AS
(
SELECT
dbo.SOInfoArchive.jobName,
dbo.SOInfoArchive.nodeID,
dbo.SOInfoArchive.nodeGroup,
dbo.SOInfoArchive.endStatus,
dbo.SOInfoArchive.returnCode,
dbo.SOInfoArchive.startTime,
dbo.SOInfoArchive.endTime,
dbo.SOInfoArchive.oDate,
dbo.SOInfoArchive.orderID,
dbo.SOInfoArchive.status,
dbo.SOInfoArchive.runCount,
dbo.SOInfoArchive.owner,
dbo.SOInfoArchive.cyclic,
dbo.SOInfoArchive.soInfoID,
dbo.SOInfoArchive.docInfoID,
dbo.SOInfoArchive.existsNote,
dbo.SOInfoArchive.noSysout,
dbo.serverInfo.serverName,
dbo.Groups.label AS applicationName,
Groups_1.label AS memberName,
Groups_2.label AS groupName,
Groups_3.label AS scheduleTableName,
dbo.SOInfoArchive.serverInfoID,
dbo.SOInfoArchive.applicationID,
dbo.SOInfoArchive.groupID,
dbo.SOInfoArchive.memberID,
dbo.SOInfoArchive.scheduleTableID,
dbo.docFile.docFileID,
dbo.docInfo.docInfoID AS Expr1,
dbo.docFile.docFileObject
FROM dbo.SOInfoArchive
JOIN dbo.serverInfo
ON dbo.SOInfoArchive.serverInfoID = dbo.serverInfo.serverInfoID
JOIN dbo.docInfo
ON dbo.SOInfoArchive.docInfoID = dbo.docInfo.docInfoID
JOIN dbo.docFile
ON dbo.docInfo.docFileID = dbo.docFile.docFileID
LEFT JOIN dbo.Groups AS Groups_3
ON dbo.SOInfoArchive.scheduleTableID = Groups_3.ID
LEFT JOIN dbo.Groups AS Groups_1
ON dbo.SOInfoArchive.memberID = Groups_1.ID
LEFT JOIN dbo.Groups AS Groups_2
ON dbo.SOInfoArchive.groupID = Groups_2.ID
LEFT JOIN dbo.Groups
ON dbo.SOInfoArchive.applicationID = dbo.Groups.ID
WHERE CONTAINS (docfileObject,#searchText)
),
cte2 AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Expr1) AS rowNum, *
FROM cte
)
SELECT rowNum,
serverName,
jobName ,
oDate,
runCount,
orderID,
applicationName,
memberName,
nodeID,
endStatus,
returnCode,
startTime,
endTime,
status,
owner,
existsNote,
docFileID
FROM cte2
WHERE rowNum > #LowerLimit
AND rowNum <= #UpperLimit
END
Probably nobody cares anymore, but the reason it didn't work is that ROW_NUMBER() returns a BIGINT and your code defines the structure this way: cast(null as int) as rowNum
SELECT Col1, Col2, Col3, Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (CASE #InvoiceMethod //Problem is Here
WHEN ''
THEN def
ELSE (#InvoiceMethod)
END)
A piece of code from the stored procedure. If am executing this, it's not returning any rows, even though it has some to return. Problem is with the IN clause, if I didn't pass anything to IN clause i.e #InvoiceMethod is null, then I'm getting rows.
If I pass anything to #InvoiceMethod, I'm not getting any rows.
The value in #InvoiceMethod is = 'A','B'
I tried many combinations like 'A','B' or "A","B" without any results.
How to pass values to IN clause please? In which format?
Please help me out of this.
Modified the stored procedure to the following,
Declare #tmpt table (value nvarchar(5) not null)
SET #InvoiceCount=(select COUNT(*) from dbo.fnSplit(#InvoiceMethod, ','))
SET #tempVar=1;
WHILE #tempVar<=(#InvoiceCount)
BEGIN
INSERT INTO #tmpt (value)
VALUES (#InvoiceMethod);//Here i need to insert array of values to temp table.like invoicemethod[0],invoicemethod[1]&invoicemethod[2] depends on #InvoiceCount
SET #tempVar=#tempVar+1;
END
--DECLARE #tmpt TABLE (value NVARCHAR(5) NOT NULL)
--INSERT INTO #tmpt (value) VALUES (#InvoiceMethod);
SELECT Col1,Col2,Col3,Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 between #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT value FROM #tmpt)
But not getting the results as expected :(
IMO this isn't a good way to approach this problem, by passing a list of filter values for a column in a comma separated string, as this is almost encouraging a Dynamic Sql approach to the problem (i.e. where you EXEC a built Sql string which pastes in the #InvoiceMethod as a string).
Instead, Sql 2008 has Table Valued Parameters, (and prior to this, you could use Xml), which allows you to pass structured data into a procedure in a table format.
You then just need to join to this table parameter to effect the 1..N valued IN () filtering.
CREATE TYPE ttInvoiceMethods AS TABLE
(
Method VARCHAR(20)
);
GO
CREATE PROCEDURE dbo.SomeProc
(
#InvoiceMethod ttInvoiceMethods READONLY, -- ... Other Params here
)
AS
begin
SELECT Col1, Col2, ...
FROM Table1
INNER JOIN #InvoiceMethod
ON Table1.def = #InvoiceMethod.Method -- Join here
WHERE User1 = #Owner
... Other Filters here
END
Have a look here for a similar solution with a fiddle.
Edit
The optional parameter (#InvoiceMethod = '') can be handled by changing the JOIN to the TVP with a subquery:
WHERE
-- ... Other filters
AND (Table1.def IN (SELECT Method FROM #InvoiceMethod))
OR #InvoiceMethod IS NULL)
To Initialize a TVP to NULL, just don't bind to it in C# at all.
I think a variable represetning multiple values with comma is not allowed in the in clause. You should either use string fiunctions (split and join) or go with the temp table solution. I prefer the second.
Use a temporary table to store your values and then pass it to your in statement
DECLARE #tmpt TABLE (value NVARCHAR(5) NOT NULL)
INSERT INTO #tmpt .........
...
...
SELECT Col1,Col2,Col3,Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT value FROM #tmpt)
Used Splitfunctions to resolve the issue,Modified SQL Query
SELECT Col1, Col2, Col3, Col4
FROM Table1
WHERE User1 = #Owner
AND group1 = #Group
AND date1 BETWEEN #startDate AND #endDate
AND Mail LIKE #email
AND def IN (SELECT * FROM sptFunction(#InvoiceMethod,',')) //Problem is Here (Solved by using split functions)
I'm having a strange problem with a linq query. I'm using LINQPad 4 to make some a query that uses regular expression using LinqToSQL as the LinqPad driver.
Here's the query that I'm trying to make :
(from match in
from s in SystemErrors
select Regex.Match(s.Description, "...")
select new
{
FamilyCode = match.Groups["FamilyCode"].Value,
ProductPrefix = match.Groups["ProductPrefix"].Value,
BillingGroup = match.Groups["BillingGroup"].Value,
Debtor = match.Groups["Debtor"].Value
}).Distinct()
As you can see I'm trying to extract data from a text description in a log table using groups. The query works, but the Distinct doesn't want to work, it returns a line for all Match.
I have read that distinct should work with anonymous type, matching each property. Even more strange is that distinct does actually do something, it orders the values alphabetically by the FamilyCode (and then by ProductPrefix, etc.).
Has someone an idea on why this isn't working?
Thanks
Here is what is displayed in the SQL tab of LinqPad :
DECLARE #p0 NVarChar(1000) = 'Big Regexp'
DECLARE #p1 NVarChar(1000) = 'FamilyCode'
DECLARE #p2 NVarChar(1000) = 'ProductPrefix'
DECLARE #p3 NVarChar(1000) = 'BillingGroup'
DECLARE #p4 NVarChar(1000) = 'Debtor'
SELECT DISTINCT [t2].[Description] AS [input], [t2].[value], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5]
FROM (
SELECT [t1].[Description], [t1].[value], #p1 AS [value2], #p2 AS [value3], #p3 AS [value4], #p4 AS [value5]
FROM (
SELECT [t0].[Description], #p0 AS [value]
FROM [SystemError] AS [t0]
) AS [t1]
) AS [t2]
var result = from eachError in SystemErrors
let match = Regex.Match(eachError.Description, "...")
group eachError by new
{
FamilyCode = match.Groups["FamilyCode"].Value,
ProductPrefix = match.Groups["ProductPrefix"].Value,
BillingGroup = match.Groups["BillingGroup"].Value,
Debtor = match.Groups["Debtor"].Value
}
into unique
select unique.key;
When you use Distinct(), it's distinct by pointer to each object, not value because select new {} is object type not value type. Try using group by instead.
On the other hand, you can use .Distinct(IEqualityComparer<T>) overload and provided EqualityComparer for the object that you want to process.
The stored procedure:
ALTER PROC [Admin].[sp_Ques]
(
#QuesID bigint
)
AS
BEGIN
IF #QuesID = 0
SET #QuesID =NULL
SELECT FQ.QuesID, FQ.Ques,QuesAns
FROM Admin.Ques FQ
WHERE FQ.QuesID = Coalesce(#QuesID,QuesID)
SELECT Language FROM Admin.Language WHERE LanguageID=FQ.LanguageID
END
In the second Select statement:
SELECT Language FROM Admin.Language WHERE LanguageID=FQ.LanguageID
In this statement, I want the value of "FQ.LanguageID" from 1st select statement, so I wrote this:-
LanguageID=FQ.LanguageID
Apparently didn't work. It says "The multi-part identifier "FQ.LanguageID" could not be bound."
Do I need to pass this LanguageID to the stored procedure as a parameter and then use it as:-
SELECT Language FROM Admin.Language WHERE LanguageID=#LanguageID
How can I make this LanguageID=FQ.LanguageID work if I don't want to pass LanguageID as the second argument to the stored procedure? Is there a way?
Perhaps create a local variable to hold the LanguageID that's being retrieved. Assign a value to it during the previous SELECT. The addition of TOP 1 simply ensures that if/when you ever have multiple matches in the first query (indeed you will when #Ques is zero or null!), only one value is returned in that query, thereby allowing a single value into your variable.
DECLARE #Lang int --whatever datatype your QuesID is.
SELECT TOP 1
FQ.QuesID, FQ.Ques,QuesAns as QuesAns,
FQ.QuesAns[Answers], FQT.QuesType ,
FQ.QuesTypeID, FQ.QuesParentID, FQ.Active, FQ.AdminLanguageID
,#Lang = FQ.AdminLanguageID
FROM Admin.Ques FQ
LEFT OUTER JOIN Admin.QuesTypes FQT ON FQT.QuesTypeID=FQ.QuesTypeID
WHERE FQ.QuesID = Coalesce(#QuesID,QuesID)
SELECT TelerikLanguage FROM Admin.Language
WHERE AdminLanguageID=#Lang
The scope of FQ is limited to the first select statement.
Your options include:
Passing AdminLanguageID as a parameter as you have suggested
Retrieving AdminLanguageID in a prior statement (select #AdminLanguageID = AdminLanguageID from...)
Joining Admin.Language with Admin.Ques
Using a subquery (select ... from Admin.Language where AdminLanguageID in (select AdminLanguageID from Admin.Ques where ...)
Why not just join them into 1 select?
ALTER PROC [Admin].[sp_Ques]
(
#QuesID bigint
)
AS
BEGIN
IF #QuesID = 0
SET #QuesID =NULL
SELECT FQ.QuesID, FQ.Ques,QuesAns as QuesAns,FQ.QuesAns[Answers], FQT.QuesType ,FQ.QuesTypeID, FQ.QuesParentID, FQ.Active,FQ.AdminLanguageID, AL.TelerikLanguage
FROM Admin.Ques FQ
LEFT OUTER JOIN Admin.QuesTypes FQT ON FQT.QuesTypeID=FQ.QuesTypeID
LEFT JOIN Admin.Language AL ON AL.AdminLanguageID=FQ.AdminLanguageID
WHERE FQ.QuesID = QuesID OR #QuesID IS NULL
END
I'm stuck on translating a left outer join from LINQToSQL that returns unique parent rows.
I have 2 tables (Project, Project_Notes, and it's a 1-many relationship linked by Project_ID). I am doing a keyword search on multiple columns on the 2 table and I only want to return the unique projects if a column in Project_Notes contains a keyword. I have this linqtoSQl sequence going but it seems to be returning multiple Project rows. Maybe do an Exist somehow in LINQ? Or maybe a groupby of some sort?
Here's the LINQToSQL:
query = from p in query
join n in notes on p.PROJECT_ID equals n.PROJECT_ID into projectnotes
from n in notes.DefaultIfEmpty()
where n.NOTES.Contains(cwForm.search1Form)
select p;
here's the SQL it produced from profiler
exec sp_executesql N'SELECT [t2].[Title], [t2].[State], [t2].[PROJECT_ID],
[t2].[PROVIDER_ID], [t2].[CATEGORY_ID], [t2].[City], [t2].[UploadedDate],
[t2].[SubmittedDate], [t2].[Project_Type]FROM ( SELECT ROW_NUMBER() OVER (ORDER BY
[t0].[UploadedDate]) AS [ROW_NUMBER], [t0].[Title], [t0].[State], [t0].[PROJECT_ID],
[t0].[PROVIDER_ID], [t0].[CATEGORY_ID], [t0].[City], [t0].[UploadedDate],
[t0].[SubmittedDate], [t0].[Project_Type] FROM [dbo].[PROJECTS] AS [t0] LEFT OUTER JOIN
[dbo].[PROJECT_NOTES] AS [t1] ON 1=1 WHERE ([t1].[NOTES] LIKE #p0) AND
([t0].SubmittedDate] >= #p1) AND ([t0].[SubmittedDate] < #p2) AND ([t0].[PROVIDER_ID] =
#p3) AND ([t0].[CATEGORY_ID] IS NULL)) AS [t2] WHERE [t2].[ROW_NUMBER] BETWEEN #p4 + 1
AND #p4 + #p5 ORDER BY [t2].[ROW_NUMBER]',N'#p0 varchar(9),#p1 datetime,#p2 datetime,#p3
int,#p4 int,#p5 int',#p0='%chicago%',#p1=''2000-09-02 00:00:00:000'',#p2=''2009-03-02
00:00:00:000'',#p3=1000,#p4=373620,#p5=20
This query returns all mutiples of the 1-many relationship in the results. I found how to do an Exists in LINQ from here. http://www.linq-to-sql.com/linq-to-sql/t-sql-to-linq-upgrade/linq-exists/
Here is the LINQToSQL using Exists:
query = from p in query
where (from n in notes
where n.NOTES.Contains(cwForm.search1Form)
select n.PROJECT_ID).Contains(p.PROJECT_ID)
select p;
The generated SQL statement:
exec sp_executesql N'SELECT COUNT(*) AS [value] FROM [dbo].[PROJECTS] AS [t0] WHERE
(EXISTS(SELECT NULL AS [EMPTY] FROM [dbo].[PROJECT_NOTES] AS [t1] WHERE
([t1].PROJECT_ID] = ([t0].[PROJECT_ID])) AND ([t1].[NOTES] LIKE #p0))) AND
([t0].[SubmittedDate] >= #p1) AND ([t0].[SubmittedDate] < #p2) AND ([t0].[PROVIDER_ID] =
#p3) AND ([t0].[CATEGORY_ID] IS NULL)',N'#p0 varchar(9),#p1 datetime,#p2 datetime,#p3
int',#p0='%chicago%',#p1=''2000-09-02 00:00:00:000'',#p2=''2009-03-02
00:00:00:000'',#p3=1000
I get a SQL timeout from the databind() from using Exists.
it seems to be returning multiple Project rows
Yes, that's how join works. If a project has 5 matching notes, it show up 5 times.
What if the problem is - "Join" is the wrong idiom!
You want to filter the projects to those whose notes contain certain text:
var query = db.Project
.Where(p => p.Notes.Any(n => n.NoteField.Contains(searchString)));
You are going to have to use the DefaultIfEmpty extension method. There are a few questions on SO already that show how to do this. Here is a good example:
How can I perform a nested Join, Add, and Group in LINQ?