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 it possible to tell linq2db to generate string literals instead of variables?

See original GitHub issue

Hello! I’ve got the following issue. I am trying to use Sql Server 2016 JSON_VALUE functionality via linq2db’s Sql.Expression attribute. I’ve created the following expression:

[Sql.Expression("JSON_VALUE(FileAttributesJsonColumn, {0})", ServerSideOnly = true)]
public static object JsonValue(string propertyName) => throw new InvalidOperationException();

Then I use it like that:

var files = fileTable.Where(_ => SqlExtensions.JsonValue("$.Extension") == "pdf").ToArray();`

In this case linq2db generates pretty valid where clause:

JSON_VALUE(FileAttributesJson, N'$.Extension') = N'pdf'

However when i try to use variable instead of string literal as property name I receive an error:

var value = "$.Extension";
var files = fileTable.Where(_ => SqlExtensions.JsonValue(value) == "pdf").ToArray();

System.Data.SqlClient.SqlException : The argument 2 of the "JSON_VALUE or JSON_QUERY" must be a string literal.

That happens because in this case linq2db generates variable:

DECLARE @value1 NVarChar(4000) -- String
SET     @value1 = N'$.Extension'

and uses it in where clause:

JSON_VALUE(FileAttributesJson, @value1) = N'pdf'

while Sql Server 2016 allows only literals as JSON_VALUE arguments.

Is there a way to tell linq2db to inject C# variable value directly into SQL string literal instead of variable? Thanks a lot!

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:23 (21 by maintainers)

github_iconTop GitHub Comments

1reaction
sdanylivcommented, Aug 30, 2018

Finally, please please please do not spend your personal and valuable time to write the whole code again. I really appreciate your assistance but at the same time aware of that you are doing it in your personal time and I am sure you have more important things to do then writing code for every question I ask you 😃

I had to. Otherwise i will spend a lot of much more time explaining. Sample is usually better 😃 Also i have never created such converters before, so it was my personal experience and i saw new ways how to improve this area.

1reaction
sdanylivcommented, Aug 27, 2018

@CoskunSunali there is closest solution that i can advise. Also you can handle Expression tree before converting SQL, but i have no time to play with that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.Expressions;
using LinqToDB.Extensions;
using LinqToDB.Mapping;
using LinqToDB.SqlQuery;
using Newtonsoft.Json;
using NUnit.Framework;

namespace Tests.Playground
{

	[AttributeUsage(AttributeTargets.Property)]
	public class JSonContentAttribute : Attribute
	{
	}

	public static class MappingHelper
	{
		private static MethodInfo _deserializeMethod;
		private static MethodInfo _serializeMethod;
		private static ConstructorInfo _dataParamContructor;

		static MappingHelper()
		{
			_deserializeMethod = MemberHelper.MethodOf(() => JsonConvert.DeserializeObject(null, typeof(int)));
			_serializeMethod = MemberHelper.MethodOf(() => JsonConvert.SerializeObject(null));
			_dataParamContructor = typeof(DataParameter).GetConstructor(new[] { typeof(string), typeof(object) });
		}

		public static void GenerateConvertorsForTables(Type dataConnectionType, MappingSchema ms)
		{
			var propsWithTables = dataConnectionType.GetProperties()
				.Where(p => typeof(IQueryable<>).IsSameOrParentOf(p.PropertyType));

			var types = propsWithTables.Select(p => p.PropertyType.GenericTypeArguments[0]).Distinct().ToArray();
			foreach (var t in types)
			{
				GenerateConvertors(t, ms);
			}
		}

		public static void GenerateConvertors(Type entityType, MappingSchema ms)
		{
			foreach (var propertyInfo in entityType.GetProperties().Where(p => p.GetCustomAttribute(typeof(JSonContentAttribute)) != null))
			{
				// emulating inParam => JsonConvert.DeserializeObject(inParam, propertyInfo.PropertyType)

				var inParam = Expression.Parameter(typeof(string));
				var lambda = Expression.Lambda(
					Expression.Convert(
						Expression.Call(null, _deserializeMethod, inParam,
							Expression.Constant(propertyInfo.PropertyType)),
						propertyInfo.PropertyType),
					inParam);

				ms.SetConvertExpression(typeof(string), propertyInfo.PropertyType, lambda);

				var inObjParam = Expression.Parameter(propertyInfo.PropertyType);
				var lambdaSql = Expression.Lambda(
					Expression.New(_dataParamContructor, Expression.Constant("p"),
						Expression.Call(null, _serializeMethod, inObjParam))
					, inObjParam);

				ms.SetConvertExpression(propertyInfo.PropertyType, typeof(DataParameter), lambdaSql);
			}
		}
	}

	public class MyDataConnection : DataConnection
	{
		private static MappingSchema _convertorSchema;

		static MyDataConnection()
		{
			_convertorSchema = new MappingSchema();
			MappingHelper.GenerateConvertorsForTables(typeof(MyDataConnection), _convertorSchema);
		}

		public MyDataConnection(string providerName, string connectionString, MappingSchema mappingSchema) : base(providerName, connectionString, mappingSchema)
		{
			AddMappingSchema(_convertorSchema);
		}

		public MyDataConnection(string configurationString) : base(configurationString)
		{
			AddMappingSchema(_convertorSchema);
		}

		public ITable<SampleClass> SampleClass => GetTable<SampleClass>();
	}

	[Table]
	public class SampleClass
	{
		[Column] public int Id    { get; set; }

		
		[Column(DataType = DataType.VarChar, Length = 4000), JSonContent]
		public DataClass Data { get; set; }
	}

	public class DataClass
	{
		public string Property1 { get; set; }
	}

	public static class Json
	{
		class JsonValueBuilder : Sql.IExtensionCallBuilder
		{
			public void Build(Sql.ISqExtensionBuilder builder)
			{
				var pathExpr = builder.Arguments[0];
				if (pathExpr.NodeType != ExpressionType.MemberAccess)
					throw new NotSupportedException();

				var pathList = new List<Expression>();
				var current = pathExpr;
				while (true)
				{
					pathList.Add(current);
					if (current.NodeType == ExpressionType.MemberAccess)
					{
						current = ((MemberExpression) current).Expression;
					}
					else
						break;
				}

				pathList.Reverse();

				var entity = pathList[0];
				var field  = pathList[1];

				var fieldSql = builder.ConvertExpressionToSql(field);
				builder.AddParameter("field", fieldSql);

				var propPathStr = "$";
				for (int i = 2; i < pathList.Count; i++)
				{
					propPathStr += "." + ((MemberExpression) pathList[i]).Member.Name;
				}

				builder.AddParameter("propPath", new SqlValue(propPathStr));
			}
		}


		[Sql.Extension("JSON_VALUE({field}, {propPath})", Precedence = Precedence.Primary, BuilderType = typeof(JsonValueBuilder), ServerSideOnly = true)]
		public static string Value(object path)
		{
			throw new NotImplementedException();
		}
	}

	[TestFixture]
	public class TestJson : TestBase
	{

		[Test, Combinatorial]
		public void SampleSelectTest([DataSources(false)] string context)
		{
			using (var db = new MyDataConnection(context))
			using (var table = db.CreateLocalTable<SampleClass>())
			{
				db.Insert(new SampleClass { Id = 1, Data = new DataClass { Property1 = "Pr1" } });

				var objects = table.Where(t => Sql.AsNotNull(Json.Value(t.Data.Property1)) == "Pr1")
					.ToArray();
			}
		}

	}
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

String matching problem in Entity framework. Works for a ...
I am trying to get a row from a table using the entity framework in C#. I have a table called "TipoPlanta" with...
Read more >
Is there a way to parameterize Contains query? · Issue #973
Currently linq query with Contains method generates sql operator "in" with inlined parameters. For example: [Test] public void ...
Read more >
C#11 Features - Raw String Literals
Raw String Literals are the new format of string literals introduced in C# 11. This new format can help us in getting the...
Read more >
Handbook - Literal Types
There are three sets of literal types available in TypeScript today: strings, numbers, and booleans; by using literal types you can allow an...
Read more >
Template literals (Template strings) - MDN Web Docs - Mozilla
Template literals are literals delimited with backtick (`) characters, allowing for multi-line strings, string interpolation with embedded ...
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