question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Is there a way to parameterize Contains query?

See original GitHub issue

Currently linq query with Contains method generates sql operator “in” with inlined parameters.

For example:

[Test]
		public void __Linq2Db_InlineParameters_For_Contains()
		{
			var ids = new int[] { 1, 2, 3 };

			using (var db = GetConnection())
			{
				var query = db.Users.Where(x => ids.Contains(x.Id));

				Console.WriteLine(query);
			}
		}

generates:

SELECT
	[t1].[Id],
	[t1].[Name],
	[t1].[IsActive],
	[t1].[AppointmentId]
FROM
	[Users] [t1]
WHERE
	[t1].[Id] IN (1, 2, 3)

Is there a way to generate something like [t1].[Id] IN (@p0, @p1, @p2)?

Environment details

linq2db version: 1.10.1 Database Server: SqlServer 2016 Database Provider: SqlServer.2012 Operating system: Windows 10 Framework version: .NET Framework 4.6.2

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:46 (22 by maintainers)

github_iconTop GitHub Comments

1reaction
arwylcommented, Jun 20, 2018

@sdanyliv you know, I think I managed to autoreplace Contains to something parameterized… To list of OR-s. Without extensions, cache disabling and stuff

If you have time, can you share your thoughts about this solution?

        // for parameterization of ConstantExpressions
	class ExpressionScopedVariables<T>
	{
		public T Value;
	}

	public class GetValuesExpressionReplacer : ExpressionVisitor
	{
		protected List<object> _values;

		public List<object> GetValues(Expression expr)
		{
			_values = new List<object>();
			Visit(expr);
			return _values;
		}

		protected override Expression VisitConstant(ConstantExpression node)
		{
			_values.Add(node.Value);
			return base.VisitConstant(node);
		}
	}

	public class ContainsToOrExpressionReplacer : ExpressionVisitor
	{
		protected Lazy<GetValuesExpressionReplacer> _getValuesExpressionReplacer = new Lazy<GetValuesExpressionReplacer>();
		protected override Expression VisitMethodCall(MethodCallExpression node)
		{
			if (node.Method.Name == "Contains")
			{
				var arg = node.Arguments[0];
				var obj = node.Object;

				// obj will be null, if Contains is called for IQueryable. That is handled ok by default
				if (obj != null && arg is MemberExpression)
				{
					IEnumerable values;

					var argMember = arg as MemberExpression;

					if (obj is MemberExpression)
					{
						var objectMember = Expression.Convert(obj, typeof(object));

						var getterLambda = Expression.Lambda<Func<object>>(objectMember);

						var getter = getterLambda.Compile();

						values = getter() as IEnumerable;
					}
					// case for NewArrayExpression, InitListExpression and so on
					// when used like: x => new List{...}.Contains(x.Id)
					else
					{
						values = _getValuesExpressionReplacer.Value.GetValues(obj);
					}

					if (values != null)
					{
						BinaryExpression res = null;

						foreach (object o in values)
						{
							var expressionScopedVariablesType = typeof(ExpressionScopedVariables<>).MakeGenericType(argMember.Type);

							var scope = Activator.CreateInstance(expressionScopedVariablesType);

							var getVariable = expressionScopedVariablesType.GetMember(nameof(ExpressionScopedVariables<object>.Value))[0];

							getVariable.SetValue(scope, o);

							var scopeExp = Expression.Constant(scope);

							var accessExpr = Expression.MakeMemberAccess(scopeExp, getVariable);

							var equalsExpr = Expression.Equal(arg, accessExpr);

							if (res == null)
							{
								res = equalsExpr;
							}
							else
							{
								res = Expression.Or(res, equalsExpr);
							}
						}

                                                  // case when res is null (Contains is called for empty collection) is handled ok by default
						if (res != null)
						{
							return res;
						}
					}

				}
			}

			return base.VisitMethodCall(node);
		}
	}

	class Linq2DbDataConnection : DataConnection, IExpressionPreprocessor
	{
		protected Lazy<ContainsToOrExpressionReplacer> _containsToOrExpressionReplacer = new Lazy<ContainsToOrExpressionReplacer>();

		public Linq2DbDataConnection(string connString) : base(connString)
		{
		}

		public Linq2DbDataConnection(IDataProvider dataProvider, string connString) : base(dataProvider, connString) { }

		/// <summary>
		/// Processes the expression.
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <returns></returns>
		public Expression ProcessExpression(Expression expression)
		{
			var replacedExpression = _containsToOrExpressionReplacer.Value.Visit(expression);
			return replacedExpression;
		}
	}

Test:

var ids = new List<int> { 1, 2, 3 };

var query = db.Users.Where(x => ids.Contains(x.Id) && x.IsActive);

var str = query.ToString();
Console.WriteLine(str);
Assert.IsTrue(str.Contains("@Value1"));
Assert.IsTrue(str.Contains("@Value2"));
Assert.IsTrue(str.Contains("@Value3"));

ids = new List<int> { 5, 6 };

query = db.Users.Where(x => ids.Contains(x.Id) && x.IsActive);

str = query.ToString();
Console.WriteLine(str);
Assert.IsTrue(str.Contains("@Value1"));
Assert.IsTrue(str.Contains("@Value2"));

query = db.Users.Where(x => new List<string> { "1", "2", "3", null }.Contains(x.Name) && x.IsActive);

str = query.ToString();
Console.WriteLine(str);
Assert.IsTrue(str.Contains("@Value1"));
Assert.IsTrue(str.Contains("@Value2"));
Assert.IsTrue(str.Contains("@Value3"));


query = db.Users.Where(x => new List<string>().Contains(x.Name) && x.IsActive);

str = query.ToString();
Console.WriteLine(str);

output:

DECLARE @Value1 Int -- Int32
SET     @Value1 = 1
DECLARE @Value2 Int -- Int32
SET     @Value2 = 2
DECLARE @Value3 Int -- Int32
SET     @Value3 = 3

SELECT
	[t1].[Id],
	[t1].[Name],
	[t1].[IsActive],
	[t1].[Guid],
	[t1].[AppointmentId]
FROM
	[Users] [t1]
WHERE
	(([t1].[Id] = @Value1 OR [t1].[Id] = @Value2) OR [t1].[Id] = @Value3) AND
	1 = [t1].[IsActive]

--  testDataProvider SqlServer.2008
DECLARE @Value1 Int -- Int32
SET     @Value1 = 5
DECLARE @Value2 Int -- Int32
SET     @Value2 = 6

SELECT
	[t1].[Id],
	[t1].[Name],
	[t1].[IsActive],
	[t1].[Guid],
	[t1].[AppointmentId]
FROM
	[Users] [t1]
WHERE
	([t1].[Id] = @Value1 OR [t1].[Id] = @Value2) AND 1 = [t1].[IsActive]

--  testDataProvider SqlServer.2008
DECLARE @Value1 NVarChar(4000) -- String
SET     @Value1 = N'1'
DECLARE @Value2 NVarChar(4000) -- String
SET     @Value2 = N'2'
DECLARE @Value3 NVarChar(4000) -- String
SET     @Value3 = N'3'

SELECT
	[t1].[Id],
	[t1].[Name],
	[t1].[IsActive],
	[t1].[Guid],
	[t1].[AppointmentId]
FROM
	[Users] [t1]
WHERE
	((([t1].[Name] = @Value1 OR [t1].[Name] = @Value2) OR [t1].[Name] = @Value3) OR [t1].[Name] IS NULL) AND
	1 = [t1].[IsActive]

--  testDataProvider SqlServer.2008
SELECT
	[t1].[Id],
	[t1].[Name],
	[t1].[IsActive],
	[t1].[Guid],
	[t1].[AppointmentId]
FROM
	[Users] [t1]
WHERE
	1 = 0 AND 1 = [t1].[IsActive]
0reactions
sdanylivcommented, Jun 21, 2018

You are right. Your transformation is not ideal and do not not handle situations with SelectMany when used Method that returns IQueryable and has Contains inside. You have to Invoke this method and grab Expression returned by this method.

I will continue to work with correct cache hit. Looks like we have problem here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use parameters to ask for input when running a query
Open the union query in SQL view. · Add a WHERE clause that contains the fields you want to add parameters to. ·...
Read more >
sql server 2008 - Parameterize an SQL IN clause
How do I parameterize a query containing an IN clause with a variable number of arguments, like this one? SELECT * FROM Tags...
Read more >
Using parameterized queries to avoid SQL injection
Parameterized queries is a technique that aims to separate the SQL query from the user input values. The user input values are passed...
Read more >
Passing parameters in CONTAINS using wildcard
I am having a difficult time trying to find a way to use an SQL string in ASP.NET to pass a parameter to...
Read more >
Using parameterized queries - Amazon Athena
You can use Athena parameterized queries to re-run the same query with different parameter values at execution time and help prevent SQL injection...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found