WHERE IN params for custom / raw sql fail?

Not sure if I should have split this into two tickets or not.

As described at the bottom, I’m trying to query for Parent + ‘First/Easliest’ Child in Ormlite. Because of the problems I had constructing the query in static typing (my attempt at bottom), I decided to instead use a raw SQL query, where the ‘complex query’ is of the form SELECT WHERE IDS IN (@IDS) has IDS as an array of GUIDS.

Everytime I attempt this however, the query fails , unable to parse char string to guid. When I attempt a similar query using WHERE IN @MyARR_OF_STRINGS, i get no results at all…

It’s as though passing lists of values as the @InIds param is being skipped, and I am strugging to understand why.

See this code and the descriptions of which queries work / return nothing unexpectedly / fail completely:

using System;
using ServiceStack;
using ServiceStack.Data;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.SqlServer;

namespace ConsoleApp1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			var connectionString = "Server=localhost;Database=mydb;User Id=sa; Password=mypass;";
			IDbConnectionFactory fac =
				new OrmLiteConnectionFactory(connectionString, SqlServer2012OrmLiteDialectProvider.Instance);

			//using (var captured = new CaptureSqlFilter())
			using (var db = fac.OpenDbConnection())
			{
				db.DropTable<FileTbl>();
				db.CreateTable<FileTbl>();

				db.Insert(new FileTbl { FileId = Guid.NewGuid(), Name = "a" });
				db.Insert(new FileTbl { FileId = Guid.NewGuid(), Name = "b" });
				db.Insert(new FileTbl { FileId = Guid.NewGuid(), Name = "c" });
				var files = db.Select<FileTbl>();
				var fileIds = files.Map(f => f.FileId);
				var fileNames = files.Map(f => f.Name);

				// First off, Regular select with no params works...
				var thisSeemsToWorkFine_ResultsAreReturned = db.Select<FileTbl>("select * from FileTbl");

				/*
				// This crashes "System.Data.SqlClient.SqlException: 'Incorrect syntax near '@FileNames'.'
				var listByTextWithboutBracesSeemsToFail = db.SqlList<FileTbl>("select * from [FileTbl] where Name in @FileNames ",
					new { FileNames = fileNames });
				*/

				var listByTextWithBracesGivesNoResultsEvenThoughIThinkItShould = db.SqlList<FileTbl>(
					"select * from [FileTbl] where Name in (@FileNames) ",
					new { FileNames = fileNames });

				var selectByTextGivesNoResultsEvenThoughIThinkItShould = db.Select<FileTbl>(
					"select * from [FileTbl] where Name in (@FileNames) ",
					new { FileNames = fileNames });


				/*
				// This crashes with: System.Data.SqlClient.SqlException: 'Conversion failed when converting from a character string to uniqueidentifier.'
				var byGuids = db.SqlList<FileTbl>("select * from [FileTbl] where FileId in (@FileIds) ", new { FileIds = fileIds });
				//captured.SqlStatements.Last().PrintDump();
				*/
			}
		}
	}


	public class FileTbl
	{
		[PrimaryKey]
		public Guid FileId { get; set; }

		public string Name { get; set; }

		public string Extension { get; set; }

		public long FileSizeBytes { get; set; }

		public bool IsDeleted { get; set; }

		[RowVersion]
		public ulong RowVersion { get; set; }
	}
}

The motivating problem for all of this is:

I have been trying without success to use Ormlite to build a typed query for Event + EventSession where EventSession is the first scheduled session for each event (e.g. I select a list of events, and then try to also load up the EventSession where Min(StartDate) is the record selected, using a groupby.

The SQL would look like this:

select ES.* 
FROM EventSession ES 
INNER JOIN 
(
	select ES.EventId, MIN(StartDtUtc) as EarliestStartUtc 
	from EventSession ES
	WHERE 
		ES.EventId in (@EventIds)
		AND ES.IsDeleted = 0
	Group By ES.EventId
) AS GRP 
ON ES.EventId = GRP.EventId AND ES.StartDtUtc = GRP.EarliestStartUtc

So what I am trying in OrmLite is something like:

// get the list of events we are interested in by categoryid
var eventRows = Db.Select<EventTbl>(e => e.EventCategoryId == eventCategoryId);
var eventIds = eventRows.Map(e => e.EventId);

// now find the first event session start date for each event....
var earliestSessionsQuery = Db.From<EventSessionTbl>()
	.Where(sd => eventIds.Contains(sd.EventId))
	.GroupBy(sd => sd.EventId)
	.Select(x => new { x.EventId, StartDate = Sql.Min(nameof(EventSessionTbl.StartDtUtc))})
	
// Now .. somehow... join that to the full EventSession record so that 
// I can get ALL the event session columns (rather than only the eventid+min_date that i could query 
// during the GROUP BY statement??
// But I can't make this work using static typing.. and as mentioned, the custom raw sql WHERE IN statement fails too...

thanks in advance, and for your patience.

Can you you provide just the code for the raw SQL Query that’s failing? it’s not clear which one it is.

Sure. For example, it seems that the arrays aren’t getting parameterized as I expect? For teh C# code:

// This crashes "System.Data.SqlClient.SqlException: 'Incorrect syntax near '@FileNames'.'
var listByTextWithboutBracesSeemsToFail = db.SqlList<FileTbl>("select * from [FileTbl] where Name in @FileNames ",
new { FileNames = fileNames });

produces (in sql server profiler):

exec sp_executesql N'select * from [FileTbl] where Name in @FileNames ',N'@FileNames varchar(7)',@FileNames='[c,b,a]'

Then for the queries SQLLIST and SELECT (as follows):

var listByTextWithBracesGivesNoResultsEvenThoughIThinkItShould = db.SqlList<FileTbl>(
"select * from [FileTbl] where Name in (@FileNames) ",
new { FileNames = fileNames });

both produce

exec sp_executesql N'select * from [FileTbl] where Name in (@FileNames) ',N'@FileNames varchar(7)',@FileNames='[a,c,b]'

And finally the command with the GUIDS,

// This crashes with: System.Data.SqlClient.SqlException: 'Conversion failed when converting from a character string to uniqueidentifier.'
var byGuids = db.SqlList<FileTbl>("select * from [FileTbl] where FileId in (@FileIds) ", new { FileIds = fileIds });

produces the sql:

exec sp_executesql N'select * from [FileTbl] where FileId in (@FileIds) ',N'@FileIds varchar(100)',@FileIds='[62ca17c292794324a7461907c9f1f9db,115040858a9c40d5b8194e9c250da167,c9a2655c8af24948ba48676432b80b1b]'

Yeah you can’t use DB Params in SQL IN like that, you can do it with a Typed query with:

var files = db.Select<FileTbl>(x => fileNames.Contains(x.Name));

Where OrmLite splits it into individual DB params with the generated SQL like:

SELECT "FileId", "Name", "Extension", "FileSizeBytes", "IsDeleted", "RowVersion" 
FROM "File"
WHERE "Name" IN (@0,@1,@2) 

How should I pass IN params to a custom raw sql query though?

The problem that I have is my query is too complex for using a typed query (or at least, I am not able to build one with my current understanding), so when trying to write a custom raw sql string, I am unsure how to pass that an array of values for an IN condition?

var eventRows = Db.Select<EventTbl>(e => e.EventCategoryId == eventCategoryId);
var eventIds = eventRows.Map(e => e.EventId);

var qry = $@"
	select ES.* 
	FROM EventSession ES 
	INNER JOIN 
	(
		select ES.EventId, MIN(StartDtUtc) as EarliestStartUtc 
		from EventSession ES
		WHERE 
			ES.EventId in (@EventIds)
			AND ES.IsDeleted = 0
		Group By ES.EventId
	) AS GRP 
	ON ES.EventId = GRP.EventId AND ES.StartDtUtc = GRP.EarliestStartUtc
	";

	// how do i use this custom sql with @EventIds ??
	db.WhatCmdNow ???

You’d need to manually split it into multiple db params like OrmLite does, so you would need to build your SQL statement so it would look like:

...
ES.EventId in (@e1, @e2, @e3)

You can generate the SQL and populate the DB params with something like:

var dbParams = new Dictionary<string, object>();
var sb = new StringBuilder();
for (var i = 0; i < fileNames.Length; i++)
{
    if (sb.Length > 0)
        sb.Append(",");
    sb.Append("@" + i);
    dbParams[i.ToString()] = fileNames[i];
}

var results = db.SqlList<EventTbl>($$"SELECT ... ES.EventId in ({sb}) ...", dbParams);

I just realized, I could probably just use the underlying Dapper functionality… so I tried:

var results = db.Query<FileTbl>("select * from [FileTbl] where FileId in @FileIds ", new { FileIds = fileIds });

But I got hit with an error I have seen before (using Dapper in a WebApi project for a client):

System.InvalidOperationException: 'Error parsing column 5 (RowVersion=System.Byte[] - Object)'

I checked that old project, and I have a note that for Dapper and EF, the RowVersion has to be declared differently to how OrmLite requires. From Dapper-based project (where I’d hoped to use SS but wasnt able to), I have note to myself:

//[CustomField("RowVersion")]		// We wanted to make ServiceStack.Ormlite write out the column creation as 
RowVersion/Timestamp, but Dapper/EF needs it to be a byte[]
public byte[] RowVersion { get; set; }

Any ideas on whether its possible to make the Dapper Byte[] concept play nice with SS Ormlite? Ie. If i declre my rowversions using SS conventions, but somehow make the Dapper QUERY call respect that format?

Otherwise, I am going to have to build up a lot of Where In conditions for this project manually, and really wanted to avoid that…

(its 3am, and I have to shelf this for now, to be continued tomorrow), but thanks.

RowVersion is a ulong in OrmLite. I’d imagine if you manually listed the columns (i.e. instead of ES.*) then Dapper wouldn’t know about or care the table has a ulong RowVersion column.

FYI I’ve just added support for auto splitting IEnumerable params in this commit.

With the latest ServiceStack v5 on MyGet you can now pass a collection of values and OrmLite will automatically split it up into multiple params and modify the SQL statement, so you can now do:

rows = db.SqlList<Event>($$"SELECT ...EventId in (@fileNames) ...", new { fileNames });
1 Like

Dude, you’re a machine. Thanks very much!