BLToolkit fuction table-valued parameter in linq
avyrupaev opened this issue · comments
I want to invoke table-valued fuction with table-valued parameter in SqlServer 2008 from BLToolkit linq query. How can I do it?
Sql functions is:
CREATE FUNCTION [dbo].[saf_get_ids_from_data_table]
(
@DataTable IDsTable READONLY
)
RETURNS TABLE
AS
RETURN
(
SELECT ID
FROM @DataTable
)
SqlServer user-defined table type is:
CREATE TYPE [dbo].[IDsTable] AS TABLE
(
ID INT PRIMARY KEY NOT NULL
)
I tried declare it in this way:
public class TableDbFunctions
{
private readonly DbManager _db;
public TableDbFunctions(DbManager db)
{
_db = db;
}
[TableFunction(Name = "dbo.saf_get_ids_from_data_table")]
public Table<IdXmlEntity> GetIdsFromDataTable(int[] ids)
{
return _db.GetTable<IdXmlEntity>(this, (MethodInfo)(MethodBase.GetCurrentMethod()), ids);
}
//or this way
[TableFunction(Name = "dbo.saf_get_ids_from_data_table")]
public Table<IdXmlEntity> GetIdsFromDataTable([ParamTypeName("IDsTable")]int[] ids)
{
return _db.GetTable<IdXmlEntity>(this, (MethodInfo)(MethodBase.GetCurrentMethod()), ids);
}
}
public class IdXmlEntity
{
public int ID { get; set; }
}
But catch ArgumentException "The table type parameter '@p1' must have a valid type name."
Found solution myself.
You need to change default Sql2008DataProvider like this:
public sealed class Sql2008WithTablesDataProvider : SqlDataProviderBase
{
static readonly List<Func<Type, string>> _udtTypeNameResolvers = new List<Func<Type, string>>();
static Sql2008WithTablesDataProvider()
{
AddUdtTypeNameResolver(ResolveStandartUdt);
}
public static void AddUdtTypeNameResolver(Func<Type, string> resolver)
{
_udtTypeNameResolvers.Add(resolver);
}
static string ResolveStandartUdt(Type type)
{
return type.Namespace == "Microsoft.SqlServer.Types" ? type.Name.Replace("Sql", "") : null;
}
public override string Name
{
get { return "Sql2008WithTables"; }
}
public override ISqlProvider CreateSqlProvider()
{
return new MsSql2008SqlProvider();
}
public override void SetParameterValue(IDbDataParameter parameter, object value)
{
base.SetParameterValue(parameter, value);
SetUdtTypeName(parameter, value);
SetTypeName(parameter, value);
}
static void SetUdtTypeName(IDbDataParameter parameter, object value)
{
var sqlParameter = parameter as System.Data.SqlClient.SqlParameter;
var valueType = value.GetType();
if (sqlParameter != null)
sqlParameter.UdtTypeName = _udtTypeNameResolvers.Select(_ => _(valueType)).FirstOrDefault(_ => !string.IsNullOrEmpty(_));
}
static void SetTypeName(IDbDataParameter parameter, object value)
{
var sqlParameter = parameter as SqlParameter;
if (value.GetType() == typeof(DataTable))
sqlParameter.TypeName = (string)((DataTable)value).ExtendedProperties["TypeName"];
}
}
Convert your collection to DataTable like this:
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string typeName)
{
var tb = new DataTable(typeof(T).Name);
tb.ExtendedProperties.Add("TypeName", typeName);
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
tb.Columns.Add(prop.Name, prop.PropertyType);
}
foreach (var item in items)
{
var values = new object[props.Length];
for (var i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
tb.Rows.Add(values);
}
return tb;
}
Declare function as
public class TableDbFunctions
{
private readonly DbManager _db;
public TableDbFunctions(DbManager db)
{
_db = db;
}
[TableFunction(Name = "dbo.saf_get_ids_from_data_table")]
public Table<IdEntity> GetIdsFromDataTable(DataTable table)
{
return _db.GetTable<IdEntity>(this, (MethodInfo)(MethodBase.GetCurrentMethod()), table);
}
}
public class IdEntity
{
public int ID { get; set; }
}
and invoke
using (var db = new DbManager(new Sql2008WithTablesDataProvider(), yourConnectionString))
{
new ColumnDbFunctions(db).GetIdsFromDataTable(numbers.Select(n => new IdEntity() { ID = n }).ToDataTable("dbo.IDsTable"));
}
Profit!