Avoid NVARCHAR when using ToList() in Linq - sql

I have a property in a class with Column(TypeName) set to VARCHAR but when the linq uses ToList(), the SQL generated by linq converts to NVARCHAR instead. Is there a way to avoid nvarchar conversion which happens when ToList() method is called ?
var codes = xyz.Where(x => x.IsValid).Select(x => x.Code.ToLower()).ToList();
requests = requests.Where(p => codes.Contains(p.Type.ToLower()));
Property(c => c.Type).HasColumnType("varchar").HasMaxLength(3).IsFixedLength();
As shown above, though the Type property has column type set to VARCHAR but linq uses NVARCHAR when ToList() is used.

You can build your own predicate:
public static class PredicateBuilder
{
private static readonly MethodInfo asNonUnicodeMethodInfo =
typeof(EntityFunctions).GetMethod("AsNonUnicode");
private static readonly MethodInfo stringEqualityMethodInfo =
typeof(string).GetMethod("op_Equality");
public static Expression<Func<TEntity, bool>> ContainsNonUnicodeString<TEntity>(
IEnumerable<string> source,
Expression<Func<TEntity, string>> expression)
{
if (source == null) throw new ArgumentNullException("source");
if (expression == null) throw new ArgumentNullException("expression");
Expression predicate = null;
foreach (string value in source)
{
var fragment = Expression.Equal(
expression.Body,
Expression.Call(null,
asNonUnicodeMethodInfo,
Expression.Constant(value, typeof(string))),
false,
stringEqualityMethodInfo);
if (predicate == null)
{
predicate = fragment;
}
else
{
predicate = Expression.OrElse(predicate, fragment);
}
}
return Expression.Lambda<Func<TEntity, bool>>(predicate,
((LambdaExpression)expression).Parameters);
}
}
See more here: https://learn.microsoft.com/da-dk/archive/blogs/diego/workaround-for-performance-with-enumerable-contains-and-non-unicode-columns-against-ef-in-net-4-0

Related

How can I bind a comma separated list in a URL to an array of objects?

I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.

Lambda string as VARCHAR

One of my Join key-selectors looks like this:
x => x.A + "-" + x.B
NHibernate makes "-" an extra parameter. This parameter gets the SQL type nvarchar and so the whole statement gets converted on the SQL Server from varchar to nvarchar.
The problem with this is, that SQL Server has a huge problem if the queried column is of type varchar instead of nvarchar. This is because the column is of another type than the parameter and so the index can't be used.
I cannot change the type of the column so I need to define somehow that NHibernate should use varchar for string literals when converting lambdas.
Any way to do this?
UPDATE
With help from Oskar Berggren I setup this classes:
public static class VarcharFix
{
/// This method returns its argument and is a no-op in C#.
/// It's presence in a Linq expression sends a message to the NHibernate Linq Provider.
public static string AsVarchar(string s)
{
return s;
}
}
public class MyHqlIdent : HqlExpression
{
internal MyHqlIdent(IASTFactory factory, string ident)
: base(HqlSqlWalker.IDENT, ident, factory)
{
}
internal MyHqlIdent(IASTFactory factory, System.Type type)
: base(HqlSqlWalker.IDENT, "", factory)
{
if (IsNullableType(type))
{
type = ExtractUnderlyingTypeFromNullable(type);
}
switch (System.Type.GetTypeCode(type))
{
case TypeCode.Boolean:
SetText("bool");
break;
case TypeCode.Int16:
SetText("short");
break;
case TypeCode.Int32:
SetText("integer");
break;
case TypeCode.Int64:
SetText("long");
break;
case TypeCode.Decimal:
SetText("decimal");
break;
case TypeCode.Single:
SetText("single");
break;
case TypeCode.DateTime:
SetText("datetime");
break;
case TypeCode.String:
SetText("string");
break;
case TypeCode.Double:
SetText("double");
break;
default:
if (type == typeof(Guid))
{
SetText("guid");
break;
}
if (type == typeof(DateTimeOffset))
{
SetText("datetimeoffset");
break;
}
throw new NotSupportedException(string.Format("Don't currently support idents of type {0}", type.Name));
}
}
private static System.Type ExtractUnderlyingTypeFromNullable(System.Type type)
{
return type.GetGenericArguments()[0];
}
// TODO - code duplicated in LinqExtensionMethods
private static bool IsNullableType(System.Type type)
{
return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
public class MyHqlCast : HqlExpression
{
public MyHqlCast(IASTFactory factory, IEnumerable<HqlTreeNode> children)
: base(HqlSqlWalker.METHOD_CALL, "method", factory, children)
{
}
public static MyHqlCast Create(IASTFactory factory, HqlExpression expression, string targetType)
{
return new MyHqlCast(factory,
new HqlTreeNode[]
{
new MyHqlIdent(factory, "cast"),
new HqlExpressionList(factory, expression,
new MyHqlIdent(factory, targetType))
});
}
}
public class MyBaseHqlGeneratorForMethod : BaseHqlGeneratorForMethod
{
public MyBaseHqlGeneratorForMethod()
: base()
{
SupportedMethods = new MethodInfo[] { typeof(VarcharFix).GetMethod("AsVarchar") };
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, HqlTreeBuilder treeBuilder, global::NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor)
{
return MyHqlCast.Create(new ASTFactory(new ASTTreeAdaptor()),
visitor.Visit(targetObject).AsExpression(),
"varchar");
}
}
public class ExtendedLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqtoHqlGeneratorsRegistry()
{
this.Merge(new MyBaseHqlGeneratorForMethod());
}
}
For now it's still not working but I see light ;)
UPDATE 2: The Query
var query = aQueryable
.Join(bQueryable,
x => x.AB, x => x.A + VarcharFix.AsVarchar("-") + x.B,
(head, middle) => new ...)
UPDATE 3:
As "-".AsVarchar() gets optimized to "-" we need a dummy parameter, which cannot be optimized like "-".AsVarchar(x.A) - that way the Linq-extension kicks in!
var query = aQueryable
.Join(bQueryable,
x => x.AB, x => x.A + "-".AsVarchar(x.A) + x.B,
(head, middle) => new ...)
There may be multiple ways to do this but here is one:
Invent your own method such as:
/// This method returns its argument and is a no-op in C#.
/// It's presence in a Linq expression sends a message to the NHibernate Linq Provider.
public static string AsVarchar(string s)
{
return s;
}
Also create a class to represent the HQL expression fragment:
public class MyHqlCast : HqlExpression
{
private MyHqlCast(IASTFactory factory, IEnumerable<HqlTreeNode> children)
: base(HqlSqlWalker.METHOD_CALL, "method", factory, children)
{
}
public static MyHqlCast Create(IASTFactory factory, HqlExpression expression,
string targetType)
{
return new MyHqlCast(factory,
new [] {
new HqlIdent(factory, "cast")),
new HqlExpressionList(factory, expression,
new HqlIdent(factory, targetType)),
});
}
}
Then derive a class from BaseHqlGeneratorForMethod. In its constructor, set the SupportedMethods property to the AsVarchar() method. Override the BuildHql() method. It should output the HQL cast constructs equivalent to cast(#param as varchar). Normally you would use the Cast() method on the treeBuilder parameter, but unfortunately this accepts just a System.Type, which isn't good enough for this case. Instead create and return an instance of your MyHqlCast:
return MyHqlCast.Create(new ASTFactory(new ASTTreeAdaptor()),
visitor.Visit(arguments[0]).AsExpression(),
"varchar");
Your implementation of BaseHqlGeneratorForMethod then needs to be registered by deriving from DefaultLinqToHqlGeneratorsRegistry. Call this.Merge(new MyGenerator()); in the constructor. Then register your registry type by
nhibernateConfiguration.LinqToHqlGeneratorsRegistry<MyRegistry>();

Can't get $filter to work with open types in WCF Data Service

I'm building a data service in WCF and I'm using a combination of reflection and open types as some of the data elements need to be created on-the-fly. Most everything is working well, but I can't get filters to work with the open type values.
The error I get is:
<message xml:lang="en-US">An error occurred while processing this request.</message>
<innererror>
<message>The method or operation is not implemented.</message>
<type>System.NotImplementedException</type>
<stacktrace> at lambda_method(Closure , GeographyProvider )
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at System.Data.Services.DataService`1.SerializeResponseBody(RequestDescription description, IDataService dataService)
at System.Data.Services.DataService`1.HandleRequest()</stacktrace>
</innererror>
I'm using an expression visitor to rewrite the LINQ expressions and it is successfully pulling the value for the open type. At this point, I'm not sure what method or operation I need to implement is. The expression tree looks like this after the expression visitor has done it's work:
Alteryx.Web.API.DatasetProvider+<GetDatasets>d__0.Where(element =>
(element.Variant == "AGSSTD_701000")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(element =>
(element.Key == "County")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(element =>
(element.Key == "36")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(it =>
Convert(((Invoke((o, name) => GetOpenValue(o, name), it, "POPCY") >= Convert(100000)) == True)))}
I've put a break point in the GetOpenValue method and it is getting called and returning the correct value. Any thoughts on where I need to go from here?
Based on Vitek's suggestions, I added checks for Convert and the comparison methods to my expression visitor, but they aren't found. Here is what my visitor code looks like:
static readonly MethodInfo GetValueOpenPropertyMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"GetValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(string) },
null
);
static readonly MethodInfo OpenConvertMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"Convert",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(ResourceType) },
null
);
static readonly MethodInfo GreaterThanOrEqualMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"GreaterThanOrEqual",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(object) },
null
);
static readonly MethodInfo EqualMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"Equal",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(object) },
null
);
static readonly Expression<Func<object, string, object>> GetValueOpenReplacement =
(o, name) => GetOpenValue(o, name);
static object GetOpenValue(object o, string name)
{
return (o as OpenDataProvider).GetValue(name);
}
static readonly Expression<Func<object, object, object>> GetGreaterThanOrEqualReplacement =
(left, right) => GetOpenGreaterThanOrEqual(left, right);
static object GetOpenGreaterThanOrEqual(object left, object right)
{
string s = left.ToString();
return true;
}
static readonly Expression<Func<object, object, object>> GetEqualReplacement =
(left, right) => GetOpenEqual(left, right);
static object GetOpenEqual(object left, object right)
{
string s = left.ToString();
return true;
}
protected override Expression VisitMethodCall(
MethodCallExpression node
)
{
if (node.Method == GetValueOpenPropertyMethodInfo)
{
// Arguments[0] - the resource to get property from
// Arguments[1] - the ResourceProperty to get
// Invoke the replacement expression, passing the
// appropriate parameters.
if (node.Arguments[0].Type.BaseType == typeof(OpenDataProvider))
{
OpenDataProvider.RequestValue(((ConstantExpression)node.Arguments[1]).Value.ToString());
}
return Expression.Invoke(
Expression.Quote(GetValueOpenReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
else if (node.Method == OpenConvertMethodInfo)
{
// Arguments[0] – the resource
// Arguments[1] – the ResourceType
// no need to do anything, so just
// return the argument
return this.Visit(node.Arguments[0]);
}
else if (node.Method == GreaterThanOrEqualMethodInfo)
{
// Invoke the replacement expression, passing the
// appropriate parameters.
return Expression.Invoke(
Expression.Quote(GetGreaterThanOrEqualReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
else if (node.Method == EqualMethodInfo)
{
// Invoke the replacement expression, passing the
// appropriate parameters.
return Expression.Invoke(
Expression.Quote(GetEqualReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
return base.VisitMethodCall(node);
}
I've put breakpoints in all of the if blocks in the VisitMethodCall method, but only the GetValueOpenProperty block is ever called.
Thanks!
Vitek kindly provided the answer to this here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/thread/bfb62cf5-48cc-4435-ae9a-76e4a13d762a
To summarize, the ExpressionVisitor needs to overide the OpenTypeMethods in the VisitBinary method.
Thanks for the help, Vitek!

To call SelectMany dynamically in the way of System.Linq.Dynamic

In System.Linq.Dynamic, there are a few methods to form Select, Where and other Linq statements dynamically. But there is no for SelectMany.
The method for Select is as the following:
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
IQueryable result = source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
return result;
}
I tried to modify the above code, after hours working, I couldn't find a way out.
Any suggestions are welcome.
Ying
Already implemented this one for our project, let me know if it works for you!
public static IQueryable SelectMany(this IQueryable source, string selector, params object[] values)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
// Parse the lambda
LambdaExpression lambda =
DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
// Fix lambda by recreating to be of correct Func<> type in case
// the expression parsed to something other than IEnumerable<T>.
// For instance, a expression evaluating to List<T> would result
// in a lambda of type Func<T, List<T>> when we need one of type
// an Func<T, IEnumerable<T> in order to call SelectMany().
Type inputType = source.Expression.Type.GetGenericArguments()[0];
Type resultType = lambda.Body.Type.GetGenericArguments()[0];
Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);
// Create the new query
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "SelectMany",
new Type[] { source.ElementType, resultType },
source.Expression, Expression.Quote(lambda)));
}
I have added another SelectMany that retuns an AnonymousType.
public static IQueryable SelectMany(this IQueryable source, string selector, string resultsSelector, params object[] values)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
// Parse the lambda
LambdaExpression lambda =
DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
// Fix lambda by recreating to be of correct Func<> type in case
// the expression parsed to something other than IEnumerable<T>.
// For instance, a expression evaluating to List<T> would result
// in a lambda of type Func<T, List<T>> when we need one of type
// an Func<T, IEnumerable<T> in order to call SelectMany().
Type inputType = source.Expression.Type.GetGenericArguments()[0];
Type resultType = lambda.Body.Type.GetGenericArguments()[0];
Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "outer"), Expression.Parameter(resultType, "inner") };
LambdaExpression resultsSelectorLambda = DynamicExpression.ParseLambda(parameters, null, resultsSelector, values);
// Create the new query
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "SelectMany",
new Type[] { source.ElementType /*TSource*/, /*,TCollection*/resultType /*TResult*/, resultsSelectorLambda.Body.Type},
source.Expression, Expression.Quote(lambda), Expression.Quote(resultsSelectorLambda)));
}
I still need to figure out how to do the following using Dynamic, the goal is to return a new result object.
var customerandorderflat = db.Customers
.SelectMany(c => c.Orders.SelectMany(o => o.Order_Details,
(ord, orddetail) => new
{
OrderID = ord.OrderID,
UnitPrice = orddetail.UnitPrice
}).DefaultIfEmpty(),
(cus, ord) => new
{
CustomerId = cus.CustomerID,
CompanyName = cus.CompanyName,
OrderId = ord.OrderID == null ? -1 : ord.OrderID,
UnitPrice = ord.UnitPrice
});
I am using the NWDB when I try:
var customerandorderquery = db.Customers .SelectMany(c => c.Orders.DefaultIfEmpty()).Select("new(CustomerId, CompanyName, OrderId)");
I get an error because CompanyName is in Customers not Orders. So it is not seeing the combination of the two objects.
When I do:
.SelectMany(c => c.Orders.DefaultIfEmpty(), (cus, ord) => new { CustomerId = cus.CustomerID, OrderId = ord.OrderID == null ? -1 : ord.OrderID });
It returns the desired result.

SQL Like keyword in Dynamic Linq

I want to use SQL's Like keyword in dynamic LINQ.
The query that I want to make is like this
select * from table_a where column_a like '%search%'
Where the column_a can be dynamically changed to other column etc
In this dynamic LINQ
var result = db.table_a.Where( a=> (a.column_a.Contains("search")) );
But the column can't be dynamically changed , only the search key can
How do we create a dynamic LINQ like
var result = db.table_a.Where("column_a == \"search\"");
That we can change the column and the search key dynamically
This should work for you:
.Where("AColumnName.Contains(#0)", "Criteria")
Create an ExtensionMethods class with this function
public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string keyword)
{
var type = typeof(T);
var property = type.GetProperty(propertyName);
string number = "Int";
if (property.PropertyType.Name.StartsWith(number))
return source;
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var constant = Expression.Constant("%" + keyword + "%");
MethodCallExpression methodExp = Expression.Call(null, typeof(SqlMethods).GetMethod("Like", new Type[] { typeof(string), typeof(string) }), propertyAccess, constant);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(methodExp, parameter);
return source.Where(lambda);
}
And then call it like this:
var result = db.table_a.Like("column_a", "%search%");
http://weblogs.asp.net/rajbk/archive/2007/09/18/dynamic-string-based-queries-in-linq.aspx
Addition:
Use an expression tree
How do I create an expression tree to represent 'String.Contains("term")' in C#?
That is what the dynamic linq library does internally.
I do not believe there is a direct translation to SQL for the LIKE keyword in LINQ. You could build one if you used expression trees, but I haven't gotten that good yet.
What I do is something like this:
using System.Data.Linq.SqlClient;
if (!string.IsNullOrEmpty(data.MailerName))
search = search.Where(a => SqlMethods.Like(a.Mailer.Name, string.Format("%{0}%", data.MailerName)));
where search is the query I'm building and data is the object containing the properties that hold the search criteria. I build the query dynamically by listing all of the possible search criteria in this way, which adds the appropriate Where methods to search.
Maybe a bit late but Another approach is add Extention method that use Contains to simulate Like keyword as :
public static class DbHelpers
{
public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string propertyValue)
{
var prop = typeof(T).GetProperty(propertyName);
if (prop == null || prop.PropertyType.Name.StartsWith("Int"))
return source;
ParameterExpression parameter = Expression.Parameter(typeof(T), "row");
Expression property = Expression.Property(parameter, propertyName);
Expression value = Expression.Constant(propertyValue);
var containsmethod = value.Type.GetMethod("Contains", new[] { typeof(string) });
var call = Expression.Call(property, containsmethod, value);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
return source.Where(lambda);
}
}
And use of it:
var foo = entity.AsQueryable().Like("Name", "bla bla");
If send PropertyName with type of int, the method return original entity that you passed before to it.