I'm having some 'tear my hair out'-problem with Entity Framework and I just can't find a solution.
What I want to do is compare strings for a search function I'm running on the server. It's basically: collection.Where(c => c.Name.Contains(searchTerm)); where searchTerm is a string passed by the user.
Everywhere I look it's either:
Change both string with toUpper(), or
Set collation to a Case Insensitive one.
However neither of these apply to my case. Here's a similar question which doesn't have an answer: Entity Framework - case insensitive Contains?
Using the first alternative would result in getting every row in the database, and then perform toUpper(), to see if it's a match. This is unacceptable performance-wise.
The second approach seems more likely to be a valid solution, but does for some reason not work. I have two databases. One local and one remote. The remote MSSQL database is set to collation: Finnish_Swedish_CI_AS, which means it's case insensitive? The local database is an auto-generated localDB with the property "Case Sensitive" set to False.
No matter which of these two databases I use it's still always Case Sensitive for the users.
Can someone please explain why this is happening so I can go on with my miserable life?
Kind regards,
Robin Dorbell
It's never been case sensitive for me, but I guess that is just how I set my database up. You can definitely use your first option of converting them both to upper case, EF doesn't pull them into memory to do that, just informs SQL server to do it. For example:
string searchTerm = "Some Text";
dbcontext.Table.Where (t => t.Column.ToLower().Contains(searchTerm.ToLower()));
Produces the following SQL (ish, i did this with linqtosql but EF should be pretty similar):
-- Region Parameters
DECLARE #p0 NVarChar(1000) = '%some text%'
-- EndRegion
SELECT *
FROM [Table] AS [t0]
WHERE LOWER([t0].[Column]) LIKE #p0
From the comments, it sounds like the OP is casting the IQueryable list to an ICollection first, meaning that any subsequent LINQ is running "locally" rather than having the chance to be converted to SQL.
For example,
// Should be IQueryable<T>
ICollection<User> users = context.Users;
// This is executed in code rather than SQL, and so is case SENSITIVE
users = users.Where(c => c.Name.Contains(searchTerm));
This may have helped debug the issue: How do I view the SQL generated by the entity framework?
Use string.Equals
collection.Where(c => string.Equals(c.Name, searchTerm, StringComparison.CurrentCultureIgnoreCase));
Also, you don't have to worry about null and get back only the information you want.
Use StringComparision.CurrentCulture for Case Sensitive.
collection.Where(c => string.Equals(c.Name, searchTerm, StringComparison.CurrentCulture));
Related
I have executed a linq query by using Entityframework like below
GroupMaster getGroup = null;
getGroup = DataContext.Groups.FirstOrDefault(item => keyword.IndexOf(item.Keywords,StringComparison.OrdinalIgnoreCase)>=0 && item.IsEnabled)
when executing this method I got exception like below
LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method, and this
method cannot be translated into a store expression.
Contains() method by default case sensitive so again I need to convert to lower.Is there any method for checking a string match other than the contains method and is there any method to solve the indexOf method issue?
The IndexOf method Of string class will not recognized by Entity Framework, Please replace this function with SQLfunction or Canonical functions
You can also take help from here or maybe here
You can use below code sample:
DataContext.Groups.FirstOrDefault(item =>
System.Data.Objects.SqlClient.SqlFunctions.CharIndex(item.Keywords, keyword).Value >=0 && item.IsEnabled)
You really only have four options here.
Change the collation of the database globally. This can be done in several ways, a simple google search should reveal them.
Change the collation of individual tables or columns.
Use a stored procedure and specify the COLATE statement on your query
perform a query and return a large set of results, then filter in memory using Linq to Objects.
number 4 is not a good option unless your result set is pretty small. #3 is good if you can't change the database (but you can't use Linq with it).
numbers 1 and 2 are choices you need to make about your data model as a whole, or if you only want to do it on specific fields.
Changing the Servers collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Database Collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Columns Collation:
http://technet.microsoft.com/en-us/library/ms190920(v=sql.105).aspx
Using the Collate statement in a stored proc:
http://technet.microsoft.com/en-us/library/ms184391.aspx
Instead you can use this method below for lowering the cases:
var lowerCaseItem = item.ToLower();
If your item is of type string. Then this might get you through that exception.
Erik Funkenbush' answer is perfectly valid when looking at it like a database problem. But I get the feeling that you need a better structure for keeping data regarding keywords if you want to traverse them efficiently.
Note that this answer isn't intended to be better, it is intended to fix the problem in your data model rather than making the environment adapt to the current (apparently flawed, since there is an issue) data model you have.
My main suggestion, regardless of time constraint (I realize this isn't the easiest fix) would be to add a separate table for the keywords (with a many-to-many relationship with its related classes).
[GROUPS] * ------- * [KEYWORD]
This should allow for you to search for the keyword, and only then retrieve the items that have that keyword related to it (based on ID rather than a compound string).
int? keywordID = DataContext.Keywords.Where(x => x.Name == keywordFilter).Select(x => x.Id).FirstOrDefault();
if(keywordID != null)
{
getGroup = DataContext.Groups.FirstOrDefault(group => group.Keywords.Any(kw => kw.Id == keywordID));
}
But I can understand completely if this type of fix is not possible anymore in the current project. I wanted to mention it though, in case anyone in the future stumbles on this question and still has the option for improving the data structure.
I have a setup on SQL Server 2008. I've got three tables. One has a string identifier as a primary key. The second table holds indices into an attribute table. The third simply holds foreign keys into both tables- so that the attributes themselves aren't held in the first table but are instead referred to. Apparently this is common in database normalization, although it is still insane because I know that, since the key is a string, it would take a maximum of 1 attribute per 30 first table room entries to yield a space benefit, let alone the time and complexity problems.
How can I write a LINQ to SQL query to only return values from the first table, such that they hold only specific attributes, as defined in the list in the second table? I attempted to use a Join or GroupJoin, but apparently SQL Server 2008 cannot use a Tuple as the return value.
"I attempted to use a Join or
GroupJoin, but apparently SQL Server
2008 cannot use a Tuple as the return
value".
You can use anonymous types instead of Tuples which are supported by Linq2SQL.
IE:
from x in source group x by new {x.Field1, x.Field2}
I'm not quite clear what you're asking for. Some code might help. Are you looking for something like this?
var q = from i in ctx.Items
select new
{
i.ItemId,
i.ItemTitle,
Attributes = from map in i.AttributeMaps
select map.Attribute
};
I use this page all the time for figuring out complex linq queries when I know the sql approach I want to use.
VB http://msdn.microsoft.com/en-us/vbasic/bb688085
C# http://msdn.microsoft.com/en-us/vcsharp/aa336746.aspx
If you know how to write the sql query to get the data you want then this will show you how to get the same result translating it into linq syntax.
var adminCov = db.SearchAgg_AdminCovs.SingleOrDefault(l => l.AdminCovGuid == covSourceGuid);
adminCov keeps coming back null. When I run SQL profiler, I can see the generated linq, when I past that into management Studio, I get the result I expect.
LinqToSql generates this:
exec sp_executesql N'SELECT [t0].[AdminCovGuid], [t0].[AdminPolicyId], [t0].[CertSerialNumber], [t0].[CertNumber], [t0].[PseudoInsurerCd], [t0].[SourceSystemCode], [t0].[CovSeqNumber], [t0].[RiderSeqNumber], [t0].[CovRiderIndicator], [t0].[CovCd], [t0].[AddrSeqNumber], [t0].[TransferSeqNumber], [t0].[CovStatusIndicator], [t0].[CovEffectiveDate], [t0].[CovExpirationDate], [t0].[CovCancelDate], [t0].[ClmIntegCode], [t0].[ClmNumber], [t0].[ClmCertSeqNumber], [t0].[TermNumber], [t0].[CovPaidThruDate], [t0].[BillThruDate], [t0].[BillModeCode], [t0].[BillModeDesc], [t0].[CalcModeCode], [t0].[CalcModeDesc], [t0].[Form1Name], [t0].[BenefitAmt], [t0].[CovDesc], [t0].[ProdLineDesc], [t0].[PremiumAmt], [t0].[PremiumTypeIndicator], [t0].[PremiumTypeDesc]
FROM [dbo].[SearchAgg_AdminCov] AS [t0]
WHERE [t0].[AdminCovGuid] = #p0',N'#p0 uniqueidentifier',#p0='D2689692-33E8-4B31-A77B-2D3A627145D4'
When I execute, I get a result. What am I missing here?
Thanks for any help,
~ck in San Diego
This is really good question. I had the same issue with Linq to SQL when selecting invoices in the date range. Some of them were not present in the object results while they were included in the generated SQL query result. I had some serious trouble with it because some invoices were not exported to the accounting software.
What I did was to create stored procedure and everything worked perfectly fine.
I would really like to know the true solution for this and why it happened.
Do you get your result back if you change your statement, as follows (note the "Equals" instead of the "==")?
var adminCov = db.SearchAgg_AdminCovs.SingleOrDefault(l => l.AdminCovGuid.Equals(covSourceGuid));
I have run into some comparison equality issues with GUIDs in the past (usually in Unit Testing), but the same might apply here.
Using Single or SingleOrDefault is always risky if there could be zero or more than one record matching the criteria. SingleOrDefault will return null if there are no matches or more than one match (in your case it could be more than one since you say there is data). This should cause a "Single" to throw an exception, if you try that. As an alternative you could try to use FirstOrDefault to get the first match if there is at least one match. It will return null when there are no matches.
Does your SearchAgg_AdminCovs table has a primary key set? I'm not sure, but I used have some headache about forget to set one, but not sure if it was select/update/insert or delete.
The SQL server doing queries based COLLATE option, so you can define how comparision will be performed (case sensitive or not). You can do it when you creating table or during query execution.
How can I control collation during my LINQ to SQL queries? Will my queries be allways case insensitive when I will do table.Column == stringValue comparison?
I don't work with the COLLATE option much, but will take my best stab at this question.
According to this article:
LINQ to SQL does not consider server settings when it translates queries.
If COLLATE is a database/table/column setting, it should just be set in the database and be ready to go when you connect.
If COLLATE is a connection setting, you can acquire the connection of your datacontext and run the command to set it. A good place to do this might be in the partial void OnCreated method.
You have to remember that L2S is a Object Relational Mapping system, so its trying to to compare objects, and translate to SQL. In L2S, if you want to compare two strings you have to ToLower() both of them for comparison.
Another thing that was a 'gotcha' for me was that in L2S, string comparison will not evaluate correctly if the comparison value you supply is null. So, in your example, if table.Column is null and stringValue is too, your query will not return the correct results (I am basing this on the assumption that stringValue is a variable defined in your code). In order to compare a string to null in L2S, you have to compare it explicitly to null: table.Column == null.
check out this article
Consider the following criteria query:
var x = SomeCriteria.AddOrder(new Order("Name", true)).List();
This will order the result set by the Name property, but case sensitive:
"A1"
"B1"
"a2"
Any ideas how to add the order case insensitive so result "a2" will end up before "B1"?
You should be able to accomplish this by ordering on a projection that normalizes the case for you. For example, Oracle has a "lower" function that will lower case string data types like varchar2 and nvarchar2; so I will use this sql function to form a projection that will order appropriately.
var projection = Projections.SqlFunction("lower",
NHibernateUtil.String,
Projections.Property("Name"));
var x = SomeCriteria.AddOrder(Orders.Asc(projection)).List()
If you're using SQL Server, I'd recommend using the "upper" function instead of "lower" for efficiency. Microsoft has optimized its native code for performing uppercase comparisons, where the rest of the world seems to have optimized on lowercase.
Hibernate (Java) has an "ignoreCase()" method on the "Order" class, but it looks like NHibernate does not have this method on its "Order."
This is how I was thinking you could do it:
var x = SomeCriteria.AddOrder(new Order("Name", true).IgnoreCase()).List();
But unfortunately, there is no IgnoreCase().
As a workaround, you could use an HQL or SQL query - either of those should allow you to order case-insensitive.
This probably depends on a case-sensitivity setting on your database server. I suspect that NHibernate just issues an "ORDER BY" clause; at least, I can't imagine what else it would do. For SQL Server, the default sort order (collation) is dictionary order, case insensitive.
This article gives some techniques for performing case sensitive searches in SQL Server. However, my advice is to sort the list that is returned by the query in code. That solution preserves the database independence of NHibernate and let's you customize the sort order per your needs.
As I know the responses to my query are always fairly small, I ended up querying the data as normal and sorting them afterwards using Linq. It works, so why bother tweaking NHibernate ;) (Using SQLite, btw)