I am using dynamic linq to parse some conditions. I wrote stored procedure and want to filter it dynamicly.
this is my procedure:
;WITH cte
AS
(
SELECT
ID
,[NO]
,Firstname
,Lastname
,PersonalNO
,ReferanceID
,CAST('' AS VARCHAR(MAX)) AS ReferanceNO
FROM dbo.Employees WHERE ReferanceID IS NULL
UNION ALL
SELECT
c.ID
,c.[NO]
,c.Firstname
,c.Lastname
,c.PersonalNO
,c.ReferanceID
,CASE
WHEN ct.ReferanceNO = ''
THEN CAST(ct.[NO] AS VARCHAR(MAX))
ELSE CAST(ct.[NO] AS VARCHAR(MAX))
END
FROM dbo.Employees c
INNER JOIN cte ct ON ct.ID = c.ReferanceID
)
SELECT * FROM cte
and in C# I am calling this procedure;
public List<Employees> GetEmployees(string searchValue, int skip, int pageSize, string sortColumn, string sortColumnDir)
{
var query = DB.sp_GetConsultants().ToList();
var totalRecords = query.Count;
query = query.Where(searchValue).ToList(); // if the searchValue is value
//"PersonalNO.Contains(\"15\")" it filters, with this kind of value
//"Lastname.Contains(\"fish\")" it dose not, but with "Fish" it does. Is the matter with uppercase?
}
and i uploaded table picture:
What is the problem?
string.Contains is case sensitive; as you noticed, searching for "fish" won't return "Fisher", even though searching for "Fish" will. There doesn't seem to be a case insensitive version in .NET (even though you can compare strings case insensitively as an option).
As a workaround, you can convert both strings to lowercase or uppercase (ToLower / ToUpper) before comparing. This might have some issues with certain non-Latin characters, however.
I think there is also a collation option in SQL Server which lets you specify the case sensitivity for strings, if you want to do the comparison at the database level instead.
Related
I am trying to get SQL to list not found when a machine name lookup is not in the database. I have the query working but I need to integrate it into C# where I have other queries. As it stands it is a lot more convoluted than I want it to be, I am guessing there is a much more intelligent and concise why of having the SQL achieve the same result.
DECLARE #myHostCount as INT
DECLARE #myHostName1 as Varchar(50);
DECLARE #myHostName2 as Varchar(50) = 'Machine220054';
DECLARE #myHostCount1 as INT
DECLARE #myHostName3 as Varchar(50);
DECLARE #myHostName4 as Varchar(50) = 'Machine22054';
SET #myHostCount1 = (SELECT COUNT(*) FROM db_owner.host WHERE name LIKE '%Machine22054%')
SET #myHostName3 = (SELECT Name FROM db_owner.host WHERE name LIKE '%Machine22054%')
SET #myHostCount = (SELECT COUNT(*) FROM db_owner.host WHERE name LIKE '%Machine220054%')
SET #myHostName1 = (SELECT Name FROM db_owner.host WHERE name LIKE '%Machine220054%')
SELECT
CASE
WHEN #myHostCount = 1 THEN 'Found'
WHEN #myHostCount = 0 THEN 'Not Found'
END AS 'Result',
#myHostName2 AS 'HostName'
UNION
SELECT
CASE
WHEN #myHostCount1 = 1 THEN 'Found'
WHEN #myHostCount1 = 0 THEN 'Not Found'
END AS 'Result',
#myHostName4 AS 'HostName'
Output:
Result Machine Name
--------------------------
Found Machine220054
Not Found Machine22054
You can try this:
DECLARE #myHostName2 as Varchar(50) = 'Machine220054';
DECLARE #myHostName4 as Varchar(50) = 'Machine22054';
DECLARE #hostName TABLE (
Name VARCHAR(255)
);
INSERT INTO #hostName (Name)
VALUES (#myHostName2), (#myHostName4)
SELECT HostName = HostName.name,
Result = IIF(COUNT(*) = 0, 'Not Found', 'Found')
FROM #hostName AS HostName
LEFT JOIN db_owner.host ON host.name LIKE '%' + HostName.Name + '%'
GROUP BY HostName.name;
First: Try to remove as much procedural logic in SQL as possible.
Second: Try to minimize the use of "Like" queries if at all possible. It will hinder indexing and cause bottlenecks eventually, or, at least limit it to ending with a "%" instead of starting it with one.
Third: you are assuming that there will only be zero or one hit in your code. What if (given either non-unique or like based queries) there are more than one?
I'd go for something like this:
declare #looking table (name varchar(50))
insert into #looking (name) values ('Machine220054'),('Machine22054')
select case when t.name is null then 'Not Found' else 'Found' end as [FoundOrNot], t.name as [FoundName],l.name as [SearchName]
from #looking as l
left outer join db_owner.host as t
on t.name like '%' + l.name + '%'
UPDATE: Tested against a random table, removed "group by" on account of either one or no hit expected.
Just to provide an update, I ended up taking the results from the DR reader and entering them into a datatable, since the dr reader can only be read once. Then I compared the selections from the listbox with the contents from the datatable then added the difference to another datatable then displayed the results in a datagrid, this produced the desired outcome of results found, being listed and the objects not found being listed as not found.
I am using a stored procedure with Dapper to retrieve data from a table. The stored procedure works fine when executed in SQL Server and returns the required information.
But when I use Dapper to run the stored procedure and retrieve a Date, the Date is returned as 01/01/0001.
Here is my stored procedure which works perfectly in SQL Server:
ALTER PROCEDURE [dbo].[spRankings_GetByEventAndGender]
#Event varchar(50),
#Gender varchar(10)
AS
BEGIN
DECLARE #event_factor INT = CASE
WHEN #Event IN ('Javelin', 'Discus', 'Shot Put', 'Hammer', 'Long Jump', 'High Jump', 'Triple Jump', 'Pole Vault')
THEN -1 /* reverse ranking = highest value is best */
ELSE 1 /* normal ranking = lowest value is best */
END;
SELECT
CASE
WHEN a.mark = ABS(b.mark)
THEN CAST(b.rank AS VARCHAR)
ELSE ''
END AS [Rank],
/*
,a.athleteid
,a.teamid
,a.eventid
*/
CASE
WHEN #event_factor = -1
THEN LTRIM(STR(a.mark, 10, 2))
ELSE FORMAT(DATEADD(SECOND, FLOOR(a.mark), 0),case when a.mark<60 then '' else 'm:' end+'ss')
+substring(ltrim((str(cast(a.mark as decimal(12,5))%1,10,2))),2,10)
end as Mark
,a.wind as Wind
,d.eventname as [Event]
,c.firstname+' '+c.lastname as Athlete
--,Convert(varchar(10),c.BirthDate,103) as [Birth Date]
,c.BirthDate as [BirthDate]
,e.teamname as [Team]
,a.venue as Venue
--, Convert(varchar(10),a.PerformanceDate,103) as [Performance Date]
,a.PerformanceDate as [Performance Date]
from dbo.Performances as a
inner join (select a.PersonId
,a.eventid
,min(a.mark*#event_factor) as mark
,rank() over(partition by a.eventid order by min(a.mark*#event_factor)) as [rank]
,avg(a.mark) as avg_mark
from dbo.Performances as a
inner join dbo.Persons as b
on b.PersonId=a.PersonId
inner join dbo.[Events] as c
on c.eventid=a.eventid
inner join dbo.Meets as d
on d.MeetId = a.MeetId
where b.gender=#Gender
and c.eventname=#Event
group by a.PersonId
,a.eventid
) as b
on b.eventid=a.eventid
and b.PersonId=a.PersonId
inner join dbo.Persons as c
on c.PersonId=a.PersonId
inner join dbo.events as d
on d.eventid=a.eventid
inner join dbo.teams as e
on e.teamid=a.teamid
inner join dbo.Meets as m
on m.MeetId = a.MeetId
order by a.eventid
,a.mark*#event_factor
,b.[rank]
/*
,b.avg_mark
,a.athleteid
*/
end
The results in SQL Server:
The method that uses Dapper to get the results:
public List<RankingsModel> GetRankingsByEventAndGender(string eventName, string gender) {
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(Helper.GetConString("GARanks"))) {
var output = connection.Query<RankingsModel>($"dbo.spRankings_GetByEventAndGender #Event, #Gender", new { Event=eventName, Gender=gender}).ToList();
return output;
}
}
The results in my application:
You are getting the default value in the application. Please find below links which can help:
Date is 01/01/0001 and not the date that is in the database?
DateTime shows the date as 01/01/0001
HTH
Thanks.
You can probably break the problem down into pieces. First make sure that Dapper is returning the right result set. You could
connection.Query<Object>
or
connection.Query<dynamic>
and inspect the result to see if it has the right DateTime value in it, in debug mode. If it does, then the problem isn't dapper retrieving the results from the database, it's that it can't map it to your POCO/class.
If the result dataset doesn't have the right value of Performance Date, then the problem is with the sql generation that Dapper is doing, and you could look into renaming the column to be without spaces, or pursue that avenue.
Good luck!
You probably have a property named 'PerformanceDate' in RankingsModel class which does not get mapped with 'Performance Date' returned from DB call.
Kindly update the SQL to return 'PerformanceDate' or make same name for both.
The other option is using Column Mapping
I have a function that takes in a collection of SearchCriteria Objects:
columnName or Key, operator (<, <=, like, etc), and value.
The function builds up a Command Object.
I made the value a command parameter and now my Unit Tests will not work for Dates.
But all of my Unit Tests work against all other datatypes like varchar.
In the debugger, one of my date unit tests that fail end up with the cmd looking like this:
SELECT * FROM (SELECT DocumentId
FROM idx1_AuthLetters a
INNER JOIN Documents b ON a.DocumentId = b.Id
WHERE Status in ('L','S','V') AND letter_date <= :1
ORDER BY DOCUMENTID )
WHERE RowNum <= 14
I did have the parameter named like :letter_date. But I might have :letter_date >= ### && :letter_date <= ### where I am looking between two dates. I cannot have the same parameter name twice so I use an i++ counter as parameter name while I am looping through all of my SearchCriteria Objects. Odd to see a parameter named like this I know but it is working for the most part.
If I take this and put in my Query Window, and look inspect the param value and plug that in:
SELECT * FROM (SELECT DocumentId
FROM idx1_AuthLetters a
INNER JOIN Documents b ON a.DocumentId = b.Id
WHERE Status in ('L','S','V') AND
letter_date <= TO_DATE('2013-1-21', 'yyyy-mm-dd')
ORDER BY DOCUMENTID )
WHERE RowNum <= 14
it works fine.
But it will not work from the C# code from my Unit Test.
Again this works for all other data types.
And it use to work before I parameterized the value in the select statement.
Exact error is:
{"ORA-01858: a non-numeric character was found where a numeric was expected"}
The answer is really simple here:
In your command, build the following string TO_DATE(:1, 'yyyy-mm-dd') . Then, just make sure that string that goes into :1 has accurate format.
Your command text should be this:
SELECT * FROM (SELECT DocumentId
FROM idx1_AuthLetters a
INNER JOIN Documents b ON a.DocumentId = b.Id
WHERE Status in ('L','S','V') AND letter_date <= TO_DATE(:1, 'yyyy-mm-dd')
ORDER BY DOCUMENTID )
WHERE RowNum <= 14
I have a database table that contains names with accented characters. Like ä and so on.
I need to get all records using EF4 from a table that contains some substring regardless of accents.
So the following code:
myEntities.Items.Where(i => i.Name.Contains("a"));
should return all items with a name containing a, but also all items containing ä, â and so on. Is this possible?
If you set an accent-insensitive collation order on the Name column then the queries should work as required.
Setting an accent-insensitive collation will fix the problem.
You can change the collation for a column in SQL Server and Azure database with the next query.
ALTER TABLE TableName
ALTER COLUMN ColumnName NVARCHAR (100)
COLLATE SQL_LATIN1_GENERAL_CP1_CI_AI NOT NULL
SQL_LATIN1_GENERAL_CP1_CI_AI is the collation where LATIN1_GENERAL is English (United States), CP1 is code page 1252, CI is case-insensitive, and AI is accent-insensitive.
I know that is not so clean solution, but after reading this I tried something like this:
var query = this.DataContext.Users.SqlQuery(string.Format("SELECT * FROM dbo.Users WHERE LastName like '%{0}%' COLLATE Latin1_general_CI_AI", parameters.SearchTerm));
After that you are still able to call methods on 'query' object like Count, OrderBy, Skip etc.
You could create an SQL Function to remove the diacritics, by applying to the input string the collation SQL_Latin1_General_CP1253_CI_AI, like so:
CREATE FUNCTION [dbo].[RemoveDiacritics] (
#input varchar(max)
) RETURNS varchar(max)
AS BEGIN
DECLARE #result VARCHAR(max);
select #result = #input collate SQL_Latin1_General_CP1253_CI_AI
return #result
END
Then add it in the DB context (in this case ApplicationDbContext) by mapping it with the attribute DbFunction:
public class ApplicationDbContext : IdentityDbContext<CustomIdentityUser>
{
[DbFunction("RemoveDiacritics", "dbo")]
public static string RemoveDiacritics(string input)
{
throw new NotImplementedException("This method can only be used with LINQ.");
}
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
And Use it in LINQ query, for example:
var query = await db.Users.Where(a => ApplicationDbContext.RemoveDiacritics(a.Name).Contains(ApplicationDbContext.RemoveDiacritics(filter))).tolListAsync();
Accent-insensitive Collation as Stuart Dunkeld suggested is definitely the best solution ...
But maybe good to know:
Michael Kaplan once posted about stripping diacritics:
static string RemoveDiacritics(string stIn)
{
string stFormD = stIn.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
for(int ich = 0; ich < stFormD.Length; ich++)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
if(uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(stFormD[ich]);
}
}
return(sb.ToString().Normalize(NormalizationForm.FormC));
}
Source
So your code would be:
myEntities.Items.Where(i => RemoveDiacritics(i.Name).Contains("a"));
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