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.

Recursive CTE results in 'types don't match' error on (MS)SQL

See original GitHub issue

My CTE query returns a concatenated string ‘Label’ that contains the parent’s label + the current label, e.g. “parentfolder/currentfolder”. This works well on SQLite, but not on (MS)SQL. There I get the following error:

Types don't match between the anchor and the recursive part in column "Label" of recursive query "CTE".

MsSQL notices that ‘Label’ is once a NVARCHAR(50) and once a NVARCHAR(MAX). Typical solution in SQL is to cast both columns to the same type (e.g. CAST(Label AS NVARCHAR(MAX)), but how to do this with C# and linq2db?

The generated SQL:

--  SqlServer.2012
WITH [CTE]
(
	[Id],
	[ParentId],
	[Level],
	[Label]
)
AS
(
	SELECT
		[c_1].[Id],
		[c_1].[ParentId],
		0,
		[c_1].[Label]
	FROM
		[TestFolders] [c_1]
	WHERE
		[c_1].[ParentId] IS NULL
	UNION ALL
	SELECT
		[c_2].[Id],
		[c_2].[ParentId],
		[r].[Level] + 1,
		[r].[Label] + Convert(VarChar(4000), N'/') + [c_2].[Label]
	FROM
		[TestFolders] [c_2]
			INNER JOIN [CTE] [r] ON [c_2].[ParentId] = [r].[Id]
)
SELECT
	[c_3].[Id],
	[c_3].[Level],
	[c_3].[ParentId],
	[c_3].[Label],
	[p].[Id],
	[p].[Label],
	[p].[ParentId]
FROM
	[CTE] [c_3]
		INNER JOIN [TestFolders] [p] ON [p].[Id] = [c_3].[Id]

Stack trace: Microsoft.Data.SqlClient.SqlException
Types don't match between the anchor and the recursive part in column "Label" of recursive query "CTE".
   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
   at Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
   at LinqToDB.Data.DataConnection.ExecuteReader(IDbCommand command, CommandBehavior commandBehavior)
   at LinqToDB.Data.DataConnection.ExecuteReader(CommandBehavior commandBehavior)
   at LinqToDB.Linq.QueryRunner.ExecuteElement[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles)
   at LinqToDB.Linq.QueryRunner.<>c__DisplayClass26_0`1.<SetRunQuery>b__0(IDataContext db, Expression expr, Object[] ps, Object[] preambles)
   at LinqToDB.Linq.ExpressionQuery`1.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.Count[TSource](IQueryable`1 source)
   at TestProject1.UnitTest1.Test1() in D:\Coding\Conx\LinqToDbCteDemo\TestProject1\UnitTest1.cs:line 88
   at TestProject1.UnitTest1.Test1() in D:\Coding\Conx\LinqToDbCteDemo\TestProject1\UnitTest1.cs:line 89
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location where exception was thrown ---
   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90

Steps to reproduce

You can find a small demonstration here: LinqToDbCteDemo (Requires some installation of MS SQL Server)

    class CteEntity<TEntity> where TEntity : class
    {
        public TEntity Entity { get; set; } = null!;
        public Guid Id { get; set; }
        public Guid? ParentId { get; set; }
        public int Level { get; set; }
        public string Label { get; set; } = null!;
    }

    class TestFolder
    {
    	public Guid Id { get; set; }
        [MaxLength(50)]
    	public string? Label { get; set; }
    	public TestFolder? Parent { get; set; }
    	public Guid? ParentId { get; set; }
    }

   await using var db = Context.CreateLinq2DbConnectionDetached();
    var q = db.GetCte<CteEntity<TestFolder>>("CTE", cte =>
    {
        return (Context.Set<TestFolder>()
                .ToLinqToDBTable()
                .Where(c => c.ParentId == null)
                .Select(c =>
                    new CteEntity<TestFolder>() {Level = 0, Id = c.Id, ParentId = c.ParentId, Label = (string)c.Label}))
            .Concat(
                Context.Set<TestFolder>()
                    .ToLinqToDBTable()
                    .SelectMany(c => cte.InnerJoin(r => c.ParentId == r.Id),
                        (c, r) => new CteEntity<TestFolder>
                        {
                            Level = r.Level + 1,
                            Id = c.Id,
                            ParentId = c.ParentId,
                            Label = r.Label + '/' + c.Label,
                        })
            );
    });

Environment details

linq2db version: 3.1.1 Database Server: MS SQL 2018 (localdb) Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Windows 10 .NET Framework: .Net Core 3.1

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
sdanylivcommented, Aug 28, 2020

We have to do correct conversion automatically. So it is a bug, but if you want you can do almost everything. Just for sample (you do not need that) Label = r.Label + Sql.Expr<string>(“Convert(NVarChar(MAX), N'/')“) + c.Label

0reactions
Corey-Mcommented, Aug 24, 2022

Ran into this one today, trying to compose a path from name fragments in a hierarchical table. If you’re not using NVARCHAR(MAX) (which I’m not) you need to wrap the length conversion around the whole expression. I ended up with this:

static partial class SqlExt
{
    [Sql.Expression("CONVERT(NVARCHAR({1}), {0})", ServerSideOnly = true)]
    public static string WithLength(this string expr, int length) 
        => throw new InvalidOperationException("Server side only.");
}

Sample usage:

class HierarchyTest
{
    public int ID { get; set; }
    public int Depth { get; set; }
    public string Path { get; set; }
}

var query = db.GetCte<HierarchyTest>(parent =>
{
    return
    (
        from row in db.SourceTable
        where row.ParentID == null
        select new HierarchyTest
        {
            ID = row.ID,
            Depth = 0,
            // invoke as extensions
            Path = row.Name.WithLength(1000)
        }
    )
    .Concat
    (
        from row in db.SourceTable
        from p in parent.InnerJoin(p => p.ID == row.ParentID)
        select new HierarchyTest
        {
            ID = row.ID,
            Depth = p.Depth,
            // alternate usage
            Path = SqlExt.WithLength(p.Path + ", " + row.Name, 1000)
        }
    )
});
Read more comments on GitHub >

github_iconTop Results From Across the Web

CTE error: "Types don't match between the anchor and ...
Msg 240, Level 16, State 1, Line 2 Types don't match between the anchor and the recursive part in column "nm" of recursive...
Read more >
Types Don't Match between the Anchor and the Recursive ...
An easy way to do this is to concatenate data from previous levels to the current row. -- This statement fails with error:...
Read more >
SQL Server Error Messages - Msg 240
SQL Server Error Messages - Msg 240 - Types don't match between the anchor and the recursive part in column "<Column Name>" of...
Read more >
SQL Server – CTE – Error – Types don't match between the ...
SQL Server – CTE – Error – Types don't match between the anchor and the recursive part in the column "ColumnName" of recursive...
Read more >
String manipulation in Recursive CTE
But SQL doesn't seem to recognize the string concatenation in the second column and returns the error: Types don't match between the anchor...
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