Entity Framework String Size Limitation on Contains Clause - c#

So, I have this lambda expression and it works just fine
list = list.Where(x => x.ListaDocumentoCaixa.Any(d => d.Observacao.Contains(term.Trim())));
I must add that this column is a varchar(6000) field. So far, this has been working just fine as I mentioned, but just recently I've ran into an issue. It seems that if the term of the search occurs from position 4001 of the string and on, the query fails to return anything to me.
After some debbuging I've found this commented on the query produced by Entity Framework
-- p__linq__0: 'maria stela gonsa' (Type = String, Size = 4000)
Then after some research I found this to be Entity's common behaviour, however, I can't have this kind of limitation on the application. My question is: Is there any way to change this behaviour ? I would like very much to avoid having to write this query as plain text and run this with ExecuteQuery if possible.
Thanks in advance for the help!

I would recommend you follow the following article, assuming you are using SQL server, about how to create a full text search index, and use it in Entity Framework with C#.
Running LIKE statements (which is what Contains() maps to) is HIGHLY inefficient on large varchar fields.
https://www.mikesdotnetting.com/article/298/implementing-sql-server-full-text-search-in-an-asp-net-mvc-web-application-with-entity-framework
EDIT: The summary of the link is:
1.) Create a full text index on the field using SQL server's wizard. That full text field will allow CONTAINS and FREETEXT searches on the whole field, and be much more efficient.
2.) Write a stored procedure that joins the table in question to results from the free text index.
3.) Make an Entity Framework class to represent results from that stored procedure, and use EF to call in and return a list of those results.

Related

How to pass list of guid as a parameter to a sql command

I need to filter a sql request by passing a list of id to , this is the command:
var command = "select Software.Name as SoftwareName,SoftwareType.Name as SoftwareType from AssetManagement.Asset as Software inner join AssetManagement.AssetType as SoftwareType on (SoftwareType.Id = Software.TypeId) where Software.Id in (#P)";
cmd.Parameters.AddWithValue("#P", authorizedSoftwaresId);
authorizedSoftwaresId is a list of string , containing data like :
"7D23968B-9005-47A9-9C37-0573629EECF9,F1982165-3F6D-4F35-A6AB-05FA116BA279"
with that it returns to me just one row, I tried adding quotes foreach value but i got "converstion from string caractere to uniqueidentifier failed " exception
This is a pretty common problem and the answer might depend on your database engine and whether you're using ADO.Net, Dapper, etc.
I'll give you some hints from my own experience with this problem on both MS Sql Server and PostgreSQL.
A lot of people think AddWithValue is a devil. You might consider Add instead.
The IN (#P) in your SQL statement might be the source of your problem. Try the Any option instead. See Difference between IN and ANY operators in SQL ; I've had success with this change in similar situations.
If all of your inputs are GUIDs, you might consider changing the type to a collection of GUIDs, although I don't think this is the fix, you have to try everything when you're stuck.
If you have to, you can parse the string version of your collection and add the ticks (') around each value. This choice has consequences, like it may prevent you from using a parameter (#P), and instead construct the final SQL statement you desire (i.e., manually construct the entire WHERE clause through string manipulations and lose the parameter.)
Good luck.

Stored Procedure sometimes returns short, sometimes returns int

I'm working with a legacy codebase and need to call a stored procedure that I'm not allowed to modify. This stored procedure returns a row or multiple rows of validation data.
Example of result set (two columns, code and text):
0 "success"
OR
3 "short error"
4 "detailed error"
In the procedure itself, the message is selected simply as:
Select 0 as code, 'success' as text
Problem:
I'm using Entity Framework to map the result of this stored procedure to a custom class:
public class ValidationResult
{
public int code { get; set; }
public string text { get; set; }
}
The call itself:
var result = context.Database.SqlQuery<ValidationResult>(#"old_sproc").ToList();
I've written some integration tests, and have noticed that when the procedure returns the success message, the 0 comes across as a short. When it returns a non-zero message, it comes across as an int. I assumed that setting code as an int, the short would fit in. Unfortunately, I get the following exception for my success test:
The specified cast from a materialized 'System.Int16' type to the 'System.Int32' type is not valid.
When I switch code to a short to make my success test pass, my failure test fails with the following exception:
The specified cast from a materialized 'System.Int32' type to the 'System.Int16' type is not valid.
ADO.NET is an answer
One solution is to fall back to ADO.NET's SqlDataReader object, so I have that as a fallback solution. I'm wondering if there is something I can do on the EF side to get this working, though.
(This is a follow-up to my previous answer. It is only relevant for sql-server-2012 and later.)
Short answer:
var sql = "EXECUTE old_sproc WITH RESULT SETS ((code INT, text VARCHAR(MAX)))";
var result = context.Database.SqlQuery<ValidationResult(sql).ToList();
Approach taken in this answer:
This answer will follow in your footsteps and use SqlQuery to execute your stored procedure. (Why not an altogether different approach? Because there might not be any alternative. I'll go into this further below.)
Let's start with an observation about your current code:
var result = context.Database.SqlQuery<ValidationResult>(#"old_sproc").ToList();
The query text "old_sproc" is really abbreviated T-SQL for "EXECUTE old_sproc". I am mentioning this because it's easy to think that SqlQuery somehow treats the name of a stored procedure specially; but no, this is actually a regular T-SQL statement.
In this answer, we will modify your current SQL only a tiny bit.
Implicit type conversions with the WITH RESULT SETS clause:
So let's stay with what you're already doing: EXECUTE the stored procedure via SqlQuery. Starting with SQL Server 2012, the EXECUTE statement supports an optional clause called WITH RESULT SETS that allows you to specify what result sets you expect to get back. SQL Server will attempt to perform implicit type conversions if the actual result sets do not match that specification.
In your case, you might do this:
var sql = "EXECUTE old_sproc WITH RESULT SETS ((code INT, text VARCHAR(MAX)))";
var result = context.Database.SqlQuery<ValidationResult(sql).ToList();
The added clause states that you expect to get back one result set having a code INT and a text VARCHAR(MAX) column. The important bit is code INT: If the stored procedure happens to produce SMALLINT values for code, SQL Server will perform the conversion to INT for you.
Implicit conversions could take you even further: For example, you could specify code as VARCHAR(…) or even NUMERIC(…) (and change your C# properties to string or decimal, respectively).
If you're using Entity Framework's SqlQuery method, it's unlikely to get any neater than that.
For quick reference, here are some quotes from the linked-to MSDN reference page:
"The actual result set being returned during execution can differ from the result defined using the WITH RESULT SETS clause in one of the following ways: number of result sets, number of columns, column name, nullability, and data type."
"If the data types differ, an implicit conversion to the defined data type is performed."
Do I have to write a SQL query? Isn't there another (more ORM) way?
None that I am aware of.
Entity Framework has been evolving in a "Code First" direction in the recent past (it's at version 6 at this time of writing), and that trend is likely to continue.
The book "Programming Entity Framework Code First" by Julie Lerman & Rowan Miller (published in 2012 by O'Reilly) has a short chapter "Working with Stored Procedures", which contains two code examples; both of which use SqlQuery to map a stored procedure's result set.
I guess that if these two EF experts do not show another way of mapping stored procedures, then perhaps EF currently does not offer any alternative to SqlQuery.
(P.S.: Admittedly the OP's main problem is not stored procedures per se; it's making EF perform an automatic type conversion. Even then, I am not aware of another way than the one shown here.)
If you can't alter the stored procedure itself, you could create a wrapper stored procedure which alters the data in some way, and have EF call that.
Not ideal of course, but may be an option.
(Note: If you're working with SQL Server 2012 or later, see my follow-up answer, which shows a much shorter, neater way of doing the same thing described here.)
Here's a solution that stays in EF land and does not require any database schema changes.
Since you can pass any valid SQL to the SqlQuery method, nothing stops you from passing it a multi-statement script that:
DECLAREs a temporary table;
EXECUTEs the stored procedure and INSERTs its result into the temporary table;
SELECTs the final result from that temporary table.
The last step is where you can apply any further post-processing, such as a type conversion.
const string sql = #"DECLARE #temp TABLE ([code] INT, [text] VARCHAR(MAX));
INSERT INTO #temp EXECUTE [old_sproc];
SELECT CONVERT(INT, [code]) AS [code], [text] FROM #temp;";
// ^^^^^^^^^^^^^ ^^^^^^^^^^^
// this conversion might not actually be necessary
// since #temp.code is already declared INT, i.e.
// SQL Server might already have coerced SMALLINT
// values to INT values during the INSERT.
var result = context.Database.SqlQuery<ValidationResult>(sql).ToList();
In the entity framework data modeler page (Model Browser), either change the functional mapping to a specific int which works for the ValidationResult class or create a new functional mapping result class which has the appropriate int and use that as the resulting DTO class.
I leave this process a touch vague because I do not have access to the actual database; instead I provide the process to either create a new functional mapping or modify an existing one. Trial and error will help you overcome the incorrect functional mapping.
Another trick to have EF generate the right information is temporarily drop the stored proc and have a new one return a stub select such as:
select 1 AS Code , 'Text' as text
RETURN ##ROWCOUNT
The reasoning for this is that sometimes EF can't determine what the stored procedure ultimately returns. If that is the case, temporarily creating the stub return and generating EF from it provides a clear picture for the mappings. Then returning the sproc to its original code after an update sometimes does the trick.
Ignore the int/short. the text is always the same for the same number right? get just the text. have a switch case. Yes its a hack but unless you can fix the root of the problem (and you say you are not allowed) then you should go with the hack that will take the least amount of time to create and will not cause problems down the road for the next person maintaining the code. if this stored proc is legacy it will not have any new kinds of results in the future. and this solution together with a nice comment solves this and lets you go back to creating value somewhere else.
Cast the static message code to an int:
Select cast(0 as int) as code, 'success' as text
This ensures the literal returned is consistent with the int returned by the other query. Leave the ValidationResult.code declared as an int.
Note: I know I missed the part in the question about the SP can't be modified, but given that this makes the answer quite complicated, I'm leaving this here for others who may have the same problem, but are able to solve it much more easily by modifying the SP. This does work if you have a return type inconsistency in the SP and modifying is an option.
There is a workaround you could use if you don't find a better solution. Let it be an int. It will work for all error codes. If you get an exception you know the result was a success so you can add a try/catch for that specific exception. It's not pretty and depending on how much this runs it might impact performance.
Another idea, have you tried changing the type of code to object?

How to do a "Contains/Like" on an EF5 Integer column vs SQL Server? e.g. Integer version of Name.Contains("Fred")

I have looked though MANY MANY posts on SO and come up with any number of different answers - none of which seem to quite work, or contradict each other based on versions of code etc.
I would prefer to steer clear of the "AsEnumerable()" fix because as I understand it this evaluates all results BEFORE the query is applied...I would like to run the query first so the data in the result is as small as possible.
For info: The tables I am querying can contain +2 million rows.
My requirement:
A "Contains" function on an Integer column of SQL Server (Compact or Standard) through Entity Framework. This would allow a user to enter a number to search on, without having the full number available. In conjunction with other predicates, this becomes very powerful in reducing the amount of data returned.
e.g.
f=>f.Id.ToString().Contains("202")<br/>
This currently fails because "ToString()" cannot be converted to an Entity Store command.
or as a T-SQL equivalent
cast(Id as varchar(9)) LIKE '%202%'
Versions:
EF5
.Net 4.0
SQL Server 2008 Standard OR SQL Compact
You can use SqlFunctions.StringConvert
f=> SqlFunctions.StringConvert((double) f.Id).Contains("202")
There is no overload for int so you have to cast it to either double or decimal
First you can try use this function: SqlFunctions.StringConvert
f=>SqlFunctions.StringConvert((double)f.id)).Contains("202")
There is no overload for int so you must typecast to double. This function is translated to a corresponding function in the database.
Another solution create an stored procedure and call it from EF:
objectContext.ExecuteSqlCommand("storedProcedureName", SqlParameters)
or
objectContext.ExecuteStoreQuery<ResultType>("storedProcedureName", SqlParameters)

Which datatype and which insertion parameter for large data

Here a field in my data records could pass the limit of 8000 chars of nvarchar, and looking for a quite larger Data-Type, e.g about 9000 chars, Any ideas ?
At first I was using NvarChar(8000), after finding some could pass this boundary I used NText
to see what will happen next, with Entity Framework seems it could do the job as it's expected without defining any Insert statement and Data Adapter, During the programming the system changed to data Adapter and I should do the job with a Insert command, Now the parameter defined is look like this :
cmdIns.Parameters.Add("#story", SqlDbType.NText, 16, "Story")
it seems that the limitation of 16 will be increased automatically while using EF is used but not with the Data Adapter(And it just insert 16 chars of the data),
really don't know (can't remember) Is the test with EF passed even the items larger than 8000 ?
If so, I'm curious about the reason.
The situation is deciding the proper Data-Type and it's equivalent working parameter to be used on insertion point of this large data field.
Note : Here SQL Server CE is Used
Edit :
Sorry, I had to go at that time,
The Data-type which should be used is NTEXT with no alternative here
but defining the **insertion Statement and parameter** is a bit hassle,
unfortunately none of the suggested methods could do the desired job similar to the piece which I gave.
without defining the length it will give errors (run-time) !
And Using AddWithValue couldn't use a the DataAdapter and do the insertion in bulk.
Maybe I have to place it in another question, but this is a piece of this question, and a working answer here could be the complete one.
Any ideas ?
If I understood your question correctly you should be fine doing something like this, omitting the size as it isn't necessary:
cmdIns.Parameters.Add( new SqlParameter( "story", SqlDbType.NText )
{
Value = yourVariable;
} );
Use AddWithValue whenever you want to add a parameter by specifying its name and value. Like this command.Parameters.AddWithValue("#story", story);

Too many parameters were provided in this RPC request. The maximum is 2100.?

A search query returned this error. I have a feeling its because the in clause is ginormous on a subordinant object, when I'm trying to ORM the other object.
Apparently in clauses shouldn't be built 1 parameter at a time. Thanks ibatis.
Your best bet is to revise your application to pass less than 2100 parameters to the stored procedure. This is a DBMS limit that can't be raised.
I got this same error when using an apparently innocent LINQ to SQL query. I just wanted to retrieve all the records whose ids were amongst the ones stored in an array:
dataContext.MyTable.Where(item => ids.Contains(item.Id)).ToArray();
It turned out that the ids array had more than 2100 items, and it seems that the DataContext adds one parameter for each item in the array in the resulting SQL query.
At the end it was a bug in my code, since the ids array had not to have so many items. But anyway it is worth to keep in mind that some extra validation is needed when using such constructs in LINQ to SQL.
You can do a few things:
Pump the params into a temp table and use said temp table to filter your query. See https://stackoverflow.com/a/9947259/37055
Create a comma-delimited array, and pass the array into SQL Server as a varchar(x). Split it out via TSQL (here are a few methods) and use the resulting rowset to filter your search results.
Have a look at your application logic. It's more than a little strange to be passing 2100 parameters to a stored procedure.
If you are passing 2100 parameters to a single stored procedure, you are simply doing something wrong. Don't raise the limit or try to work around this abomination, figure out how to do things right.

Categories

Resources