10

I Think I am currently experiencing a bug in Entity Framework 6 and possibly ADO.NET. Since there is a deadline I am not sure I can wait for this bug to be fixed and hopefully someone can help me with a clean work around.

The problem is that the query uses the values 1 and 5 in places where it should be 0.01 and 0.05. However weirdly enough 0.1 seems to be working

The generated query currently is:(gotten from SQL Server Profiler)

declare @p3  dbo.someUDT
insert into @p3 values(NULL,5)
insert into @p3 values(5,0.10)
insert into @p3 values(NULL,1)
insert into @p3 values(1,2)

exec sp_executesql N'Select * from @AName',N'@AName  [dbo].[someUDT] READONLY',@AName=@p3

While the correct code would be:

declare @p3  dbo.someUDT
insert into @p3 values(NULL,0.05)
insert into @p3 values(0.05,0.10)
insert into @p3 values(NULL,0.01)
insert into @p3 values(0.01,0.02)

exec sp_executesql N'Select * from @AName',N'@AName  [dbo].[someUDT] READONLY',@AName=@p3

I already created an issue on github here : User defined table inserting wrong value

I want to use a user defined table in my parameterized query, this question explains how this is done : Entity Framework Stored Procedure Table Value Parameter

This is the C# code used to get the SQL code above

DataTable dataTable = new DataTable();
dataTable.Columns.Add("value1", typeof(decimal));
dataTable.Columns.Add("value2", typeof(decimal));

dataTable.Rows.Add(null,0.05m); 
dataTable.Rows.Add(0.05m,0.1m); 
dataTable.Rows.Add(null,0.01m); 
dataTable.Rows.Add(0.01m,0.02m); 
List<SqlParameter> Parameters = new List<SqlParameter>();

Parameters.Add(new SqlParameter("@AName", SqlDbType.Structured) { Value = dataTable , TypeName= "dbo.someUDT" });

dbContext.Database.ExecuteSqlCommand("Select * from @AName", Parameters.ToArray());

And SQL code to get the user defined table

CREATE TYPE [dbo].[someUDT] AS TABLE
(
   [value1] [decimal](16, 5) NULL,
   [value2] [decimal](16, 5) NULL
)

EDIT:
Gert Arnold figured it out. Based on his answer I found an existing report here SQL Server Profiler TextData Column handles Decimal Inputs Incorrectly

Joost K
  • 1,096
  • 8
  • 23
  • 2
    can you try this `dataTable.Rows.Add(null,0.05m); ` and check what query it generates – rjs123431 Dec 03 '19 at 08:26
  • 1
    @rjs123431 I tried that before and it gives the same result – Joost K Dec 03 '19 at 08:27
  • 1
    You want to create a new table and return all values of the table? Sorry, but I don't understand what you really want. Can you share what is your principal goal with this? – Lutti Coelho Dec 05 '19 at 16:43
  • 1
    @LuttiCoelho sorry for the confusion, the `Select * from @AName` is as placeholder. I am actually joining on the table in a bigger query that I didn't think was relevant to the question since this already replicates the issue in a simpler format. – Joost K Dec 05 '19 at 16:46
  • 1
    So you want to insert this data from your datatable to a SQL table and then select all data from the SQL table. Is it? – Lutti Coelho Dec 05 '19 at 16:57
  • 1
    @LuttiCoelho I want to be able to use the datatable within in SQL to do whatever I want. So I want to be able to perferm Select, Delete, Join etc..(especially join) – Joost K Dec 05 '19 at 16:59
  • 1
    This means that I want the datatable within a table variable or temp table – Joost K Dec 05 '19 at 17:00
  • 1
    Ok. The first problem that it see is you are using a parameter as table name. It's not possible. This kind of sqlCommand should be done as a procedure. The question you quote is doing this. – Lutti Coelho Dec 05 '19 at 17:03
  • 1
    @LuttiCoelho I know the question did that but that's not the issue here. The code in syntactically correct and it runs. Using it with a stored procedure would result in the same incorrect values. – Joost K Dec 05 '19 at 17:06
  • 1
    Sorry. Now I realize the real problem you have. Can you try force the value to be a decimal using this dataTable.Rows.Add(0.01m,0.02m); The default type of 0.02 is float. – Lutti Coelho Dec 05 '19 at 17:26
  • 2
    The strange thing is, I indeed see the incorrect SQL, but when I use `Database.SqlQuery` (rather than `Database.ExecuteSqlCommand`) I receive the correct values in the client! – Gert Arnold Dec 05 '19 at 21:16
  • 1
    there is a level of precision that you can configure either through fluent api or code first DataAnnotations. https://stackoverflow.com/questions/3504660/decimal-precision-and-scale-in-ef-code-first, please ensure it is not this... very much sounds like this seeing as you are working with decimals. if it is this i will post as answer and you can mark as the correct answer. – Seabizkit Dec 12 '19 at 07:16
  • @Seabizkit I don't know how to apply that to my code since the objects I'm using aren't defined by Entity Framework. I'm using a DataTable without any POCOs – Joost K Dec 12 '19 at 08:25
  • configure EF to use the same level of precision 16, 5 as your SQL. Your code looks extremely unfamiliar to me and I know EF very well. How does adding data to a datatable, translate to `insert into @p3 values(NULL,0.05)` i can not see this convention or am i aware of this. Either there is code missing or i am missing something. if there is some weird way that you can do what you are, then again configure EF to be aware of precision levels. no POCOs, but exciting through EF 'dbContext.Database.ExecuteSqlCommand', so would do this to rule it out – Seabizkit Dec 12 '19 at 08:39
  • nvm i dont think this is the issue, i think the already marked answer is actually correct, you were using incorrect values to represent what you intended. apologies. – Seabizkit Dec 12 '19 at 08:47

2 Answers2

11

It's a weird Sql Profiler artifact. The values are transferred correctly. I can demonstrate that by creating a database with your user-defined type and one little table:

CREATE TABLE [dbo].[Values](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Value] [decimal](16, 5) NOT NULL,
 CONSTRAINT [PK_Values] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
GO

And inserting a couple of values:

Id          Value
----------- ---------------------------------------
1           10.00000
2           1.00000
3           0.10000
4           0.01000

Then I run your code, slightly adapted:

DataTable dataTable = new DataTable();
dataTable.Columns.Add("value1", typeof(decimal));
dataTable.Columns.Add("value2", typeof(decimal));

dataTable.Rows.Add(0.001m, 0.03m);
List<SqlParameter> Parameters = new List<SqlParameter>();

Parameters.Add(new SqlParameter("@AName", SqlDbType.Structured) { Value = dataTable, TypeName = "dbo.someUDT" });

using(var context = new MyContext(connStr))
{
    var query = "Select v.Id from dbo.[Values] v, @AName a "
        + " where v.Value BETWEEN a.value1 AND a.value2";
    var result = context.Database.SqlQuery<int>(query, Parameters.ToArray());
}

(MyContex is just a class inheriting from DbContext and nothing else)

There is only one value between 0.001m and 0.03m and that's exactly what the query returns: 4.

However, Sql Server profiler logs this:

declare @p3 dbo.someUDT
insert into @p3 values(1,3) -- See here: the log is warped

exec sp_executesql N'Select v.Value from dbo.[Values] v, @AName a  where v.Value BETWEEN a.value1 AND a.value2',N'@AName [dbo].[someUDT] READONLY',@AName=@p3

And in SSMS that returns record #2.

I think it has to do with regional settings and decimal separators getting mixed up with decimal group separators somewhere in the logging.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • 1
    I never thought about the issue being in logging. Great out of the box thinking and thank you for solving this! – Joost K Dec 06 '19 at 08:00
  • 2
    Based on your answer I got this bug report https://feedback.azure.com/forums/908035-sql-server/suggestions/38357785-sql-server-profiler-textdata-column-handles-decima Seems like I wasn't the only one. – Joost K Dec 06 '19 at 08:36
1

Honestly, I have not the same problem as you:

This is my Profiler Log :

declare @p3 dbo.someUDT
insert into @p3 values(NULL,0.05)
insert into @p3 values(0.05,0.10)
insert into @p3 values(NULL,0.01)
insert into @p3 values(0.01,0.02)

exec sp_executesql N'Select * from @AName',N'@AName [dbo].[someUDT] READONLY',@AName=@p3

I tried EntityFramework version 6.2.0 & 6.3.0 & 6.4.0 and none of these shows the problem :

DataTable dataTable = new DataTable();
dataTable.Columns.Add("value1", typeof(decimal));
dataTable.Columns.Add("value2", typeof(decimal));

dataTable.Rows.Add(null, 0.05);
dataTable.Rows.Add(0.05M, 0.1M);
dataTable.Rows.Add(null, 0.01);
dataTable.Rows.Add(0.01, 0.02);
List<SqlParameter> Parameters = new List<SqlParameter>();

Parameters.Add(new SqlParameter("@AName", SqlDbType.Structured) { Value = dataTable, TypeName = "dbo.someUDT" });

var dbContext = new test01Entities();
dbContext.Database.ExecuteSqlCommand("Select * from @AName", Parameters.ToArray());

Also, I test the ADO.NET and have same result:

SqlConnection cn = new SqlConnection("Data Source=(local);Initial Catalog=Test01;Integrated Security=true;");
using (var cmd = new SqlCommand("[foo]", cn))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cn.Open();
    cmd.Parameters.AddWithValue("@param1", 0.02);
    cmd.Parameters.AddWithValue("@param2", 0.020);
    cmd.ExecuteNonQuery();
}

I am using Visual Studio 2017, .NET Framework 4.6.1 and Microsoft SQL Server Enterprise (64-bit)

XAMT
  • 1,515
  • 2
  • 11
  • 31
  • 4
    I'm pretty sure it's related to language settings (machine vs database), so you're just lucky. – Gert Arnold Dec 07 '19 at 19:46
  • 2
    I have to add that me and @GertArnold both live in the same country making that a very likely explanation why we can both reproduce the problem – Joost K Dec 07 '19 at 23:16
  • 2
    @GertArnold Please make a sample (VS Solution + SQL Database) and share it. I will find the clue. – XAMT Dec 08 '19 at 06:37
  • 1
    @XAMT It's a Sql Server Profiler bug so it's out of our hands. If you like you can play with your machine's and database server's language settings to see when the bug appears while you run your code, but IMO that's in the pastime department. – Gert Arnold Dec 08 '19 at 10:02