how to search string in Linq to SQL using contains - c#

I am using below code to search a particular string in multiple columns
IEnumerable<UserProductDetailResult> _query
= from eml in dc.UserProductDetails
join zk in dc.ZeekUsers on eml.aspnet_User.UserId equals zk.UserId
where eml.aspnet_User.LoweredUserName.Equals(strUserName.ToLower())
&& (eml.Username.Contains(strSearch)
|| eml.ProductURL.Contains(strSearch)
|| eml.Nickname.Contains(strSearch))
&& !eml.IsDeleted
&& eml.IsActive
select new UserProductDetailResult
{
_userProductDetail = eml,
_zeekUser = zk
};
where dc is DataContext object.
but this returns 0 results.
the query that is generated from above code is
SELECT [t0].[UPID], [t0].[UserId], [t0].[PID], [t0].[Nickname], [t0].[Username], [t0].[ProductURL], [t0].[StartDt], [t0].[EndDt], [t0].[IsActive], [t0].[InfoData], [t0].[SocialNetworkingData], [t0].[AnalyticKey], [t0].[ProfileID], [t0].[UseDefaultAd], [t0].[UseDefaultEmail], [t0].[IsDeleted], [t0].[CreateDt], [t0].[LastUpdateDt], [t1].[ID], [t1].[UserId] AS [UserId2], [t1].[AccountType], [t1].[FirstName], [t1].[LastName], [t1].[Phone], [t1].[Address1], [t1].[Address2], [t1].[City], [t1].[State], [t1].[ZIP], [t1].[CountryID], [t1].[NickName1], [t1].[Nickname2], [t1].[AlternameEmail], [t1].[ProfileImage], [t1].[ZeekIdStatus], [t1].[RefZeekUserId], [t1].[IsActive] AS [IsActive2], [t1].[FailureCount], [t1].[IsBlocked], [t1].[IsFirstVisit], [t1].[IsWizardPassed], [t1].[IPAddress], [t1].[TimeZoneID], [t1].[CreateDt] AS [CreateDt2], [t1].[LastUpdateDt] AS [LastUpdateDt2]
FROM [dbo].[UserProductDetails] AS [t0]
INNER JOIN [dbo].[ZeekUsers] AS [t1] ON ((
SELECT [t2].[UserId]
FROM [dbo].[aspnet_Users] AS [t2]
WHERE [t2].[UserId] = [t0].[UserId]
)) = [t1].[UserId]
INNER JOIN [dbo].[aspnet_Users] AS [t3] ON [t3].[UserId] = [t0].[UserId]
WHERE ([t3].[LoweredUserName] = 'username') AND (([t0].[Username] LIKE 'a') OR ([t0].[ProductURL] LIKE 'a') OR ([t0].[Nickname] like 'a')) AND (NOT ([t0].[IsDeleted] = 1)) AND ([t0].[IsActive] = 1)
As soon as remove below search lines it works and returns ,
&& (eml.Username.Contains(strSearch)
|| eml.ProductURL.Contains(strSearch)
|| eml.Nickname.Contains(strSearch))
but this doesn't allow me to search
Can anyone please tell me how should I proceed?

By your generated query, I think you are using the linq-to-sql. You could use the SqlMethods.Like to generate the right like query operator, from MSDN:
Determines whether a specific character string matches a specified
pattern. This method is currently only supported in LINQ to SQL
queries.
For sample:
// first sample, any part of string
strSearch = string.Format("%[^a-zA-Z]{0}[^a-zA-Z]%", strSearch);
// on the end of the string
strSearch = string.Format("%[^a-zA-Z]{0}", strSearch);
//on the begining of the string
strSearch = string.Format("{0}[^a-zA-Z]%", strSearch);
in your query statemant..
(SqlMethods.Like(eml.Username, strSearch)
|| SqlMethods.Like(eml.ProductURL, strSearch)
|| SqlMethods.Like(eml.Nickname, strSearch))
Otherwise you could add the % char in your strSearch string before your query to result a query with information in any part of string, for sample:
strSearch = string.Contat("%", strSearch, "%");

I have create a nuget package that can help you here. It will enable you to use the following syntax:
var result = dc.UserProductDetails
.Where(eml => eml.IsActive && !eml.IsDeleted)
.Search(eml => eml.aspnet_User.LoweredUserName) // Search LoweredUsername
.IsEqual(strUserName) // when equals strUsername
.Search(eml => eml.UserName, // Search UserName
eml => eml.ProductURL, // OR ProductUrl
eml => eml.Nickname) // OR Nickname
.Containing(strSearch) // When contains strSearch
.Select(eml => new UserProductDetailResult // Build result...
{
_userProductDetail = eml
});
You can download the package from here
http://www.nuget.org/packages/NinjaNye.SearchExtensions
...also take a look at the GitHub page for more detailed information
Hope this helps

Related

How to format linq to replace AND with OR when combining multiple Where's

I have a Asp.net MVC C# app that contains a search page with the following fields:
Search.cshtml
The input values can contain only portions eg. Carnet Number could be 'Wakka 12' and in the database there is a 'Wakka 1234' record that must be returned. In SQL it would be Where [CarnetNumber] like '%Wakka 123%'
Further the fields must be used in typical OR combinations eg. if the user enters a value in the Carnet Number field and a value in the Holder field, the result should be all records that have Carnet Numbers that contains the Carnet Number entered OR records where the Holder field contains the Holder input value - SQL Where [CarnetNumber] like '%Wakka 123%' OR [Holder] like '%some holder%'
Fields that have no supplied value should not be added to the query.
My difficulty is getting the OR part working in linq. I have looked at Dynamic linq and expressions but am unable to get it to work.
This is the closest I got to a solution so far:
var Q = (from Cx in db.Carnets
where Cx.CarnetNumber.Contains(carnet.CarnetNumber)
|| Cx.Holder.Contains(carnet.Holder)
|| Cx.ImportSerial.Contains(carnet.ImportSerial)
|| Cx.CPDRegistrationNumber.Contains(carnet.CPDRegistrationNumber)
|| Cx.CPDChassis.Contains(carnet.CPDChassis)
|| Cx.CPDEngine.Contains(carnet.CPDEngine)
|| Cx.CPDMake.Contains(carnet.CPDMake)
select Cx);
This gives me a SQL query :
SELECT
[Extent1].[CarnetNumber] AS [CarnetNumber],
[Extent1].[CarnetType] AS [CarnetType],
[Extent1].[Holder] AS [Holder],
[Extent1].[ValidUntillDate] AS [ValidUntillDate],
[Extent1].[IssuingAuthority] AS [IssuingAuthority],
[Extent1].[Currency] AS [Currency],
[Extent1].[Value] AS [Value],
[Extent1].[PortOfImport] AS [PortOfImport],
[Extent1].[ImportSerial] AS [ImportSerial],
[Extent1].[ImportDate] AS [ImportDate],
[Extent1].[PortOfExport] AS [PortOfExport],
[Extent1].[ExportSerial] AS [ExportSerial],
[Extent1].[DateOfExport] AS [DateOfExport],
[Extent1].[CarnetItems] AS [CarnetItems],
[Extent1].[CPDRegistrationNumber] AS [CPDRegistrationNumber],
[Extent1].[CPDChassis] AS [CPDChassis],
[Extent1].[CPDEngine] AS [CPDEngine],
[Extent1].[CPDMake] AS [CPDMake],
[Extent1].[CPDTransportType] AS [CPDTransportType],
[Extent1].[AcquittalStatus] AS [AcquittalStatus],
[Extent1].[LastDateProcessed] AS [LastDateProcessed],
[Extent1].[LastAccessedByUser] AS [LastAccessedByUser]
FROM [dbo].[Carnet] AS [Extent1]
WHERE ([Extent1].[CarnetNumber] LIKE #p__linq__0 ESCAPE N'~')
OR ([Extent1].[Holder] LIKE #p__linq__1 ESCAPE N'~')
OR ([Extent1].[ImportSerial] LIKE #p__linq__2 ESCAPE N'~')
OR ([Extent1].[CPDRegistrationNumber] LIKE #p__linq__3 ESCAPE N'~')
OR ([Extent1].[CPDChassis] LIKE #p__linq__4 ESCAPE N'~')
OR ([Extent1].[CPDEngine] LIKE #p__linq__5 ESCAPE N'~')
OR ([Extent1].[CPDMake] LIKE #p__linq__6 ESCAPE N'~')
Now if I run this in SQL and replace the values (#p_linq_#) I get nothing back because the other fields that were not supplied are NULL. SQL is doing a LIKE null test.
I tried separating the .Where clause but the OR changed into AND's:
string search = "";
var Q = db.Carnets
.Where(a => a.CarnetNumber != null);
if (carnet.CarnetNumber != null)
{
search = carnet.CarnetNumber.Trim();
Q = Q.Where(a => a.CarnetNumber.Contains(search));
}
if (carnet.Holder != null)
{
search = carnet.Holder.Trim();
Q = Q.Where(a => a.Holder.Contains(search));
}
This gives SQL Where :
FROM [dbo].[Carnet] AS [Extent1]
WHERE ([Extent1].[CarnetNumber] LIKE #p__linq__0 ESCAPE N'~')
AND ([Extent1].[Holder] LIKE #p__linq__1 ESCAPE N'~')
If I add .TRIM() in C# the SQL Where goes bonkers and converts it into a CHARINDEX query, hence the search var.
I think the first attempt is closer to home but how do I remove all fields in the Where part that contains a Null field from my linq?
so your problem is as you already mentioned that you are performing checks on values that are null.
in your case you should check if these values you want to compare on are not null or just a whitespace before comparing them.
this should be an attempt for you
var Q = (from Cx in db.Carnets
where
(!string.IsNullOrWhitespace(carnet.CarnetNumber) && Cx.CarnetNumber.Contains(carnet.CarnetNumber))
|| (!string.IsNullOrWhitespace(carnet.Holder) && Cx.Holder.Contains(carnet.Holder))
|| (!string.IsNullOrWhitespace(carnet.ImportSerial) && Cx.ImportSerial.Contains(carnet.ImportSerial))
|| (!string.IsNullOrWhitespace(carnet.CPDRegistrationNumber) && Cx.CPDRegistrationNumber.Contains(carnet.CPDRegistrationNumber))
|| (!string.IsNullOrWhitespace(carnet.CPDChassis) && Cx.CPDChassis.Contains(carnet.CPDChassis))
|| (!string.IsNullOrWhitespace(carnet.CPDEngine) && Cx.CPDEngine.Contains(carnet.CPDEngine))
|| (!string.IsNullOrWhitespace(carnet.CPDMake) && Cx.CPDMake.Contains(carnet.CPDMake))
select Cx);
if Linq cannot translate string.IsNullOrWhitespace() function you have to make the check outside of the actual query. So you basically build up your sql query dependend on several if's and in the end you execute that query.
try this
var Q = from Cx in db.Carnets select Cx;
if (!string.IsNullOrWhitespace(carnet.CarnetNumber))
{
var withCarnetNumber = from Cx in Q
where Cx.CarnetNumber.Contains(carnet.CarnetNumber)
select Cx;
}
if (!string.IsNullOrWhitespace(carnet.Holder))
{
var withCarnetHolder = from Cx in Q
where Cx.CarnetHolder.Contains(carnet.Holder)
select Cx;
}
...
var result = withCarnetNumber.Union(withCarnetHolder).Union(...).ToList();

Comparing two counts in LINQ

hi have a simple query that compares all sent out requests to those that have been downloaded.
i have a tsql that works perfectly like so:
select case when (select COUNT(*) from FileRecipient where File_ID = 3 and downloaded = 'Y')
= (select COUNT(*) from FileRecipient where File_ID = 3) then 'Y' else 'N' end
but would like to do it in LINQ.
any and all help would be appreciated. i tried the following, but obviously i'm a long way away from getting it right. this just gives me the first of the two counts. do i need to do two separate statements to get the second?
public static bool DownloadedByAll(int fsID)
{
using (SEntities ctx = CommonS.GetSContext())
{
var result = (from fr in ctx.FileRecipients
where fr.File_ID == fsID && fr.downloaded == "Y"
select fr.Recipient_ID).Count();
}
}
or would it just be simpler for me to use tsql within the entity framework for this?
This should get you the result you need
var result = ctx.FileRecipients
.Count(f=>f.File_ID == 3
&& f.downloaded == 'Y') == ctxt.FileRecipients
.Count(f=>f.File_ID == 3);

Writing the LINQ equaivalent of a SQL join

This is the SQL query I have written:
select * from Addresses a
join ProviderAddresses pa on a.address_k = pa.address_k
where pa.provider_k = 'ABC123'
and pa.active = 1
and a.active = 1
and pa.addresstype_rtk = 'HOME'
And this is the LINQ query I wrote for it:
public IQueryable<Addresses> GetAddressesesForProvider(string provider_k, string addresstype_rtk)
{
var query = from a in this.Context.Addresses
join pa in this.Context.ProviderAddresses on a.Address_K equals pa.Address_K
where pa.AddressType_RTK == addresstype_rtk
&& pa.Active == true
&& a.Active == true
select a;
return query;
}
But it is wrong. The LINQ one return thousands of records and the SQL one returns only one record.
It is IQueryable because later I need to go through its results with a for-each loop.
In the SQL I am passing hard coded values for testing but in my code for me LINQ method I also pass the same hard coded values so that is not the issue.
Looks like you may have just missed adding the provider_k condition to the where;
public IQueryable<Addresses> GetAddressesesForProvider(string provider_k, string addresstype_rtk)
{
var query = from a in this.Context.Addresses
join pa in this.Context.ProviderAddresses on a.Address_K equals pa.Address_K
where pa.Provider_K == provider_k &&
pa.AddressType_RTK == addresstype_rtk &&
pa.Active == true &&
a.Active == true
select a;
return query;
}

Using Intersect I'm getting a Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator

I'm using a Linq to SQL query to provide a list of search term matches against a database field. The search terms are an in memory string array. Specifically, I'm using an "intersect" within the Linq query, comparing the search terms with a database field "Description". In the below code, the description field is iss.description. The description field is separated into an array within the Linq query and the intersect is used to compare the search terms and description term to keep all of the comparing and conditions within the Linq query so that the database is not taxed. In my research, trying o overcome the problem, I have found that the use of an in-memory, or "local" sequence is not supported. I have also tried a few suggestions during my research, like using "AsEnumerable" or "AsQueryable" without success.
searchText = searchText.ToUpper();
var searchTerms = searchText.Split(' ');
var issuesList1 = (
from iss in DatabaseConnection.CustomerIssues
let desc = iss.Description.ToUpper().Split(' ')
let count = desc.Intersect(searchTerms).Count()
where desc.Intersect(searchTerms).Count() > 0
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SearchHits = count,
SolutionDesc = (solution.Description == null)? "No Solutions":solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null)? false : stTois.Successful
}).ToList();
...
The only way I have been successful is to create two queries and calling a method as shown below, but this requires the Linq Query to return all of the matching results (with the number of hits for search terms in the description) including the non-matched records and provide an in-memory List<> and then use another Linq Query to filter out the non-matched records.
public static int CountHits(string[] searchTerms, string Description)
{
int hits = 0;
foreach (string item in searchTerms)
{
if (Description.ToUpper().Contains(item.Trim().ToUpper())) hits++;
}
return hits;
}
public static List<IssuesAndSolutions> SearchIssuesAndSolutions(string searchText)
{
using (BYCNCDatabaseDataContext DatabaseConnection = new BYCNCDatabaseDataContext())
{
searchText = searchText.ToUpper();
var searchTerms = searchText.Split(' ');
var issuesList1 = (
from iss in DatabaseConnection.CustomerIssues
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SearchHits = CountHits(searchTerms, iss.Description),
SolutionDesc = (solution.Description == null)? "No Solutions":solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null)? false : stTois.Successful
}).ToList();
var issuesList = (
from iss in issuesList1
where iss.SearchHits > 0
select iss).ToList();
...
I would be comfortable with two Linq Queries, but with the first Linq Query only returning the matched records and then maybe using a second, maybe lambda expression to order them, but my trials have not been successful.
Any help would be most appreciated.
Ok, so after more searching more techniques, and trying user1010609's technique, I managed to get it working after an almost complete rewrite. The following code first provides a flat record query with all of the information I am searching, then a new list is formed with the filtered information compared against the search terms (counting the hits of each search term for ordering by relevance). I was careful not to return a list of the flat file so there would be some efficiency in the final database retrieval (during the formation of the filtered List<>). I am positive this is not even close to being an efficient method, but it works. I am eager to see more and unique techniques to solving this type of problem. Thanks!
searchText = searchText.ToUpper();
List<string> searchTerms = searchText.Split(' ').ToList();
var allIssues =
from iss in DatabaseConnection.CustomerIssues
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SolutionDesc = (solution.Description == null) ? "No Solutions" : solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null) ? false : stTois.Successful
};
List<IssuesAndSolutions> filteredIssues = new List<IssuesAndSolutions>();
foreach (var issue in allIssues)
{
int hits = 0;
foreach (var term in searchTerms)
{
if (issue.IssueDesc.ToUpper().Contains(term.Trim())) hits++;
}
if (hits > 0)
{
IssuesAndSolutions matchedIssue = new IssuesAndSolutions();
matchedIssue.IssueID = issue.IssueID;
matchedIssue.IssueDesc = issue.IssueDesc;
matchedIssue.SearchHits = hits;
matchedIssue.CustomerID = issue.CustomerID;
matchedIssue.AssemblyID = issue.AssemblyID;
matchedIssue.DateOfIssue = issue.DateOfIssue;
matchedIssue.DateOfResolution = issue.DateOfResolution;
matchedIssue.CostOFIssue = issue.CostOFIssue;
matchedIssue.ProductID = issue.ProductID;
filteredIssues.Add(matchedIssue);
}
}

Searching for text in a database with Entity Framework

I'm writing a UI that allows a someone to lookup users by their first and/or last name. For example, if you typed in "Mike" for the first name and "Jo" for the last name, it would return "Mike Jones", "Mike Johnson" and "Mike Jobs". I use the following LINQ statement for this search:
var users = (from u in context.TPM_USER
where u.LASTNAME.ToLower().Contains(LastName.ToLower())
&& u.FIRSTNAME.ToLower().Contains(FirstName.ToLower())
select u);
(There may or may not be a better way to do a case-insensitive like clause, but this seems to work)
The problem is if the user types in a first or last name, but then leaves the other field empty. If I type in "Mike" for the first name and leave the Last Name field blank, I want to return all Mikes regardless of their last name. The above query returns no results unless both fields are filled in with at least something.
I tried:
var users = (from u in context.TPM_USER
where (LastName == "" || u.LASTNAME.ToLower().Contains(LastName.ToLower()))
&& (FirstName == "" || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
select u);
However, I still get no results unless both fields are filled out. I've verified under the debugger that LastName == "" is indeed true.
UPDATE:
I did some more debugging and this is actually an Oracle issue. The query being generated is:
--Replaced the field list with * for brevity
SELECT * FROM TPMDBO.TPM_USER "Extent1"
WHERE (('jones' = '') OR ((INSTR(LOWER("Extent1".LASTNAME), LOWER('jones'))) > 0)) AND (('' = '') OR ((INSTR(LOWER("Extent1".FIRSTNAME), LOWER(''))) > 0))
Which at first glance appears to be correct. However, Oracle does not seem to correctly short-circuit the phrase ('' = ''). In fact, if I do:
select * from TPM_USER where '' = ''
I get zero rows. I'm not enough of an Oracle expert to know how this query should be written, but either way it's an Entity Framework dialect bug.
Just add the predicates conditionally:
var users = from u in context.TPM_USER select u;
if (!string.IsNullOrWhiteSpace(FirstName))
users = users.Where(u => u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()));
if (!string.IsNullOrWhiteSpace(LastName))
users = users.Where(u => u.LASTNAME.ToLower().Contains(LastName.ToLower()));
Or only the LASTNAME predicate as conditional one.
Later addition:
An expression like Where(u => u.FIRSTNAME.ToLower()... is better to be avoided. They cause any indexes on FIRSTNAME to be ignored, because the field value is converted first and then compared (see here for more details).
There's a big chance you don't need these lower-case conversions. Check the database collation of the field. If it's case-insensitive (CI), which it probably is, you don't need these conversions.
Are you sure that FirstName and LastName aren't null?
You might try writing it like this instead...
string LowerFirstName = (FirstName + "").ToLower();
string LowerLastName = (LastName + "").ToLower();
var users = (from u in context.TPM_USER
where (LowerLastName == "" || u.LASTNAME.ToLower().Contains(LowerLastName))
&& (LowerFirstName == "" || u.FIRSTNAME.ToLower().Contains(LowerFirstName))
select u);
FYI, if anyone runs into this issue with Oracle, here's a workaround:
var users = (from u in context.TPM_USER
where (LastName == null|| u.LASTNAME.ToLower().Contains(LastName.ToLower()))
&& (FirstName == null || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
select u);
This will get converted to:
'' is null
In SQL, which Oracle interprets as true.
You could simply create a conditional statement around your query:
if (String.IsNullOrWhiteSpace(LastName) && !String.IsNullOrWhiteSpace(FirstName))
{
var users = (from u in context.TPM_USER
where (u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
select u);
}
else if (String.IsNullOrWhiteSpace(FirstName) && !String.IsNullOrWhiteSpace(LastName))
{
var users = (from u in context.TPM_USER
where (u.LASTNAME.ToLower().Contains(LastName.ToLower()))
select u);
}
May be you can try checking the length of the search terms to see if it is working in Oracle PL/SQL.
var users = (from u in context.TPM_USER
where ((LastName ?? "").Trim().Length == 0 || u.LASTNAME.ToLower().Contains(LastName.ToLower()))
&& ((FirstName ?? "").Trim().Length == 0 || u.FIRSTNAME.ToLower().Contains(FirstName.ToLower()))
select u);

Categories

Resources