Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidOperationException when using Poco-Class as Query Parameter #2546

Closed
BenKmann opened this issue Oct 13, 2020 · 7 comments · Fixed by #2552
Closed

InvalidOperationException when using Poco-Class as Query Parameter #2546

BenKmann opened this issue Oct 13, 2020 · 7 comments · Fixed by #2552
Assignees
Labels
area: linq status: has-pr There is active PR for issue type: bug
Milestone

Comments

@BenKmann
Copy link

This code:

var container = new MyPocoClass();
container.MyStringProperty = "Hello World";

var something = (from x in db.MyPocoClass
				    where x.MyStringProperty != container.MyStringProperty
				   select x).ToList();

Generates this Exception:

Exception message: Code supposed to be unreachable
Stack trace:
   at System.Linq.Expressions.Compiler.StackSpiller.RewriteExpression(Expression node, Stack stack)
   at System.Linq.Expressions.Compiler.StackSpiller.RewriteUnaryExpression(Expression expr, Stack stack)
   at System.Linq.Expressions.Compiler.StackSpiller.RewriteExpression(Expression node, Stack stack)
   at System.Linq.Expressions.Compiler.StackSpiller.RewriteExpressionFreeTemps(Expression expression, Stack stack)
   at System.Linq.Expressions.Compiler.StackSpiller.Rewrite[T](Expression`1 lambda)
   at System.Linq.Expressions.Expression`1.Accept(StackSpiller spiller)
   at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, DebugInfoGenerator debugInfoGenerator)
   at LinqToDB.Linq.Builder.ExpressionBuilder.CreateParameterAccessor(IDataContext dataContext, Expression accessorExpression, Expression originalAccessorExpression, Expression dbDataTypeAccessorExpression, Expression expression, ParameterExpression expressionParam, ParameterExpression parametersParam, ParameterExpression dataContextParam, String name)
   at LinqToDB.Linq.Builder.ExpressionBuilder.PrepareConvertersAndCreateParameter(ValueTypeExpression newExpr, Expression valueExpression, String name, ColumnDescriptor columnDescriptor, BuildParameterType buildParameterType)
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildParameter(Expression expr, ColumnDescriptor columnDescriptor, Boolean forceConstant, BuildParameterType buildParameterType)
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertToSql(IBuildContext context, Expression expression, Boolean unwrap, ColumnDescriptor columnDescriptor, Boolean isPureExpression)
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertCompare(IBuildContext context, ExpressionType nodeType, Expression left, Expression right)
   at LinqToDB.Linq.Builder.ExpressionBuilder.ConvertPredicate(IBuildContext context, Expression expression)
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSearchCondition(IBuildContext context, Expression expression, List`1 conditions, Boolean isNotExpression)
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildWhere(IBuildContext parent, IBuildContext sequence, LambdaExpression condition, Boolean checkForSubQuery, Boolean enforceHaving)
   at LinqToDB.Linq.Builder.WhereBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo)
   at LinqToDB.Linq.Builder.MethodCallBuilder.BuildSequence(ExpressionBuilder builder, BuildInfo buildInfo)
   at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo)
   at LinqToDB.Linq.Builder.ExpressionBuilder.Build[T]()
   at LinqToDB.Linq.Query`1.CreateQuery(IDataContext dataContext, Expression expr)
   at LinqToDB.Linq.Query`1.GetQuery(IDataContext dataContext, Expression& expr)
   at LinqToDB.Linq.ExpressionQuery`1.GetQuery(Expression& expression, Boolean cache)
   at LinqToDB.Linq.ExpressionQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at DbTasks.Program.Main(String[] args) in C:\DbTasks\Program.cs:line 104

But this Code works:

var container = new MyPocoClass();
container.MyStringProperty = "Hello World";
string myTestString = container.MyStringProperty;

var something = (from x in db.MyPocoClass
				    where x.MyStringProperty != myTestString
				   select x).ToList();

Apparently Linq2Db tries to translate the property of the POCO, although I only want the content. As long as i assign it to a variable before it is happy.
MyPocoClass.MyStringProperty is mapped with this code, which is the reason it crashes apparently:

propertybuilder.IsExpression(x => Sql.Property<object>(x, otherPropertyNameWhichIsMappedToADbColumn), false);

We do that, because the Property is only there for convenience and this mapping helps us to have two properties for this one column. It normally works.
Now I think, because "MyStringProperty" is not a column, he crashes, when he sees it as a Value on the right side. And apparently the Expressionbuilder tries to build this property as "a column" instead of "a value".

If i remove the mapping code

// propertybuilder.IsExpression(x => Sql.Property<object>(x, otherPropertyName), false);

and make it a normal Db-Column, the Expressionbuilder is happy and accepts "container.MyStringProperty" and correctly makes it to a parameter in the query.

I think this is a bug, because the Expressionbuilder is apparently able to build a query with this property and he shoud not even try to build an expression out of container.MyStringProperty.

Environment details

linq2db version: 3.1.3
Database Server: Sql-Server 2019
Database Provider: Sql-Server 2019
Operating system: Windows 10
.NET Framework: 4.5.2

@sdanyliv
Copy link
Member

Will handle that, thanks for reporting.

@sdanyliv
Copy link
Member

Please include small mapping sample with model. It is not clear what happened here.

@BenKmann
Copy link
Author

You are crazy fast! Sure will try to make a simple mapping sample.

@BenKmann
Copy link
Author

BenKmann commented Oct 13, 2020

I Modified a Test which you use in the class public class EagerLoadingTests.
I added public string? Value2 { get; set; } in Masterclass.
This reproduces the crash for me.

[Table]
		class MasterClass
		{
			[Column] [PrimaryKey] public int Id1 { get; set; }
			[Column] [PrimaryKey] public int Id2 { get; set; }
			[Column] public string? Value { get; set; }
			public string? Value2 { get; set; }

			[Column] public byte[]? ByteValues { get; set; }

			[Association(ThisKey = nameof(Id1), OtherKey = nameof(DetailClass.MasterId))]
			public List<DetailClass> Details { get; set; } = null!;

			[Association(QueryExpressionMethod = nameof(DetailsQueryImpl))]
			public DetailClass[] DetailsQuery { get; set; } = null!;

			static Expression<Func<MasterClass, IDataContext, IQueryable<DetailClass>>> DetailsQueryImpl()
			{
				return (m, dc) => dc.GetTable<DetailClass>().Where(d => d.MasterId == m.Id1 && d.MasterId == m.Id2 && d.DetailId % 2 == 0);
			}
		}

		[Test]
		public void Test([IncludeDataSources(TestProvName.AllSQLite)] string context)
		{
			var (masterRecords, detailRecords) = GenerateData();
			var intParam = 0;

			using (new AllowMultipleQuery())
			using (var db = GetDataContext(context))
			using (var master = db.CreateLocalTable(masterRecords))
			using (var detail = db.CreateLocalTable(detailRecords))
			{
				db.MappingSchema
				  .GetFluentMappingBuilder()
				  .Entity<MasterClass>()
				  .Property(m => m.Value2)
					.HasSkipOnInsert(true)
					.HasSkipOnUpdate(true)
					.IsExpression(x => Sql.Property<object>(x, "Value"), false);

				var container = new MasterClass();
				container.Value2 = "Hello World";

                                // this crashes 
				var something = (from x in db.GetTable<MasterClass>()
								 where x.Value2 != container.Value2
								 select x).ToList();

                                // this works
				string valueString = container.Value2;
				var something2 = (from x in db.GetTable<MasterClass>()
								 where x.Value2 != valueString
								 select x).ToList();
				}
		}

@sdanyliv
Copy link
Member

One question, why Sql.Property<object>(x, "Value") but not Sql.Property<string>(x, "Value") ?

@BenKmann
Copy link
Author

BenKmann commented Oct 13, 2020

This code normally runs in a foreach and is dynamically build fitting to the Database it is connected to.
I can't dynamically change the type "T" in the Sql.Property in a foreach as far as i know.
But you are right, in this case i know it and can set it to Sql.Property<string> 👍
It still crashes, but now with a different exception new LinqException("'Property' is only server-side method.");
But why does he even try?

@BenKmann
Copy link
Author

BenKmann commented Oct 13, 2020

@sdanyliv i have modified the example a little, to get it closer to my real problem.
We have columns mapped to an enum AND to the string Value we get from the db.
So we have the Option to use both, because 95% of the Values in the DB are in the enum and for the rest we have the string-property.

So same Example as before, but "Value2" is our NoColumn Enum Property (instead of string).

public enum TestEnum
		{
			a,
			b,
			c,
			d
		}

		[Table]
		class MasterClass
		{
			[Column] [PrimaryKey] public int Id1 { get; set; }
			[Column] [PrimaryKey] public int Id2 { get; set; }
			[Column] public string? Value { get; set; }
			public TestEnum? Value2 { get; set; }

			[Column] public byte[]? ByteValues { get; set; }

			[Association(ThisKey = nameof(Id1), OtherKey = nameof(DetailClass.MasterId))]
			public List<DetailClass> Details { get; set; } = null!;

			[Association(QueryExpressionMethod = nameof(DetailsQueryImpl))]
			public DetailClass[] DetailsQuery { get; set; } = null!;

			static Expression<Func<MasterClass, IDataContext, IQueryable<DetailClass>>> DetailsQueryImpl()
			{
				return (m, dc) => dc.GetTable<DetailClass>().Where(d => d.MasterId == m.Id1 && d.MasterId == m.Id2 && d.DetailId % 2 == 0);
			}
		}

And the Mapping code is now easier and i can reproduce it with this mapping code as well

db.MappingSchema
  .GetFluentMappingBuilder()
  .Entity<MasterClass>()
  .Property(m => m.Value2)
	.HasSkipOnInsert(true)
	.HasSkipOnUpdate(true)
	.IsExpression(x => x.Value, false);

var container = new MasterClass();
container.Value2 = TestEnum.a;
var enumValue = container.Value2;

// this works
var something = (from x in db.GetTable<MasterClass>()
				 where x.Value2 != enumValue
				 select x).ToList();

// this doesn't
var something2 = (from x in db.GetTable<MasterClass>()
                 where x.Value2 != container.Value2
				  select x).ToList();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: linq status: has-pr There is active PR for issue type: bug
Development

Successfully merging a pull request may close this issue.

3 participants