Benutzerdefinierte Tabelle in Entity Framework, die eine falsche Abfrage generiert

10

Ich glaube, ich habe derzeit einen Fehler in Entity Framework 6 und möglicherweise in ADO.NET. Da es eine Frist gibt, bin ich mir nicht sicher, ob ich warten kann, bis dieser Fehler behoben ist, und hoffentlich kann mir jemand bei einer sauberen Lösung helfen.

Das Problem ist, dass die Abfrage die Werte 1 und 5 an Stellen verwendet, an denen sie 0,01 und 0,05 betragen sollte. Allerdings scheint seltsamerweise 0.1 zu funktionieren

Die generierte Abfrage lautet derzeit: (vom SQL Server Profiler abgerufen)

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

Während der richtige Code wäre:

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

Ich habe hier bereits ein Problem mit github erstellt: Benutzerdefinierte Tabelle, die einen falschen Wert einfügt

Ich möchte eine benutzerdefinierte Tabelle in meiner parametrisierten Abfrage verwenden. In dieser Frage wird erläutert, wie dies getan wird: Wertparameter der Tabelle für gespeicherte Prozedurentitäten in Entity Framework

Dies ist der C # -Code, der zum Abrufen des obigen SQL-Codes verwendet wird

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());

Und SQL-Code, um die benutzerdefinierte Tabelle zu erhalten

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

EDIT:
Gert Arnold hat es herausgefunden. Basierend auf seiner Antwort habe ich hier einen vorhandenen Bericht gefunden. Die SQL Server Profiler TextData-Spalte behandelt Dezimaleingaben falsch

Joost K.
quelle
2
dataTable.Rows.Add(null,0.05m);
Können
1
@ rjs123431 Ich habe das schon einmal versucht und es gibt das gleiche Ergebnis
Joost K
1
Sie möchten eine neue Tabelle erstellen und alle Werte der Tabelle zurückgeben? Entschuldigung, aber ich verstehe nicht, was Sie wirklich wollen. Können Sie uns mitteilen, was Ihr Hauptziel ist?
Lutti Coelho
1
@LuttiCoelho Entschuldigung für die Verwirrung, die Select * from @ANameals Platzhalter ist. Ich nehme tatsächlich an einer größeren Abfrage teil, die meiner Meinung nach für die Frage nicht relevant war, da dies das Problem bereits in einem einfacheren Format repliziert.
Joost K
2
Das Seltsame ist, ich sehe zwar das falsche SQL, aber wenn ich Database.SqlQuery(anstatt Database.ExecuteSqlCommand) verwende, erhalte ich die richtigen Werte im Client!
Gert Arnold

Antworten:

11

Es ist ein seltsames SQL Profiler-Artefakt. Die Werte werden korrekt übertragen. Ich kann das demonstrieren, indem ich eine Datenbank mit Ihrem benutzerdefinierten Typ und einer kleinen Tabelle erstelle:

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

Und ein paar Werte einfügen:

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

Dann führe ich Ihren Code aus, leicht angepasst:

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());
}

( MyContexist nur eine Klasse, die von DbContextund sonst nichts erbt )

Es gibt nur einen Wert zwischen 0.001mund 0.03m und genau das gibt die Abfrage zurück : 4.

Der SQL Server-Profiler protokolliert jedoch Folgendes:

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

Und in SSMS gibt das Datensatz Nr. 2 zurück.

Ich denke, es hat mit regionalen Einstellungen und Dezimaltrennzeichen zu tun, die irgendwo in der Protokollierung mit Dezimalgruppentrennzeichen verwechselt werden.

Gert Arnold
quelle
1
Ich habe nie daran gedacht, dass das Problem bei der Protokollierung liegt. Tolles Out-of-the-Box-Denken und danke, dass Sie dieses Problem gelöst haben!
Joost K
2
Basierend auf Ihrer Antwort habe ich diesen Fehlerbericht erhalten. Feedback.azure.com/forums/908035-sql-server/suggestions/… Scheint, als wäre ich nicht der einzige.
Joost K
1

Ehrlich gesagt habe ich nicht das gleiche Problem wie Sie:

Dies ist mein Profiler-Protokoll:

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

Ich habe EntityFramework Version 6.2.0 & 6.3.0 & 6.4.0 ausprobiert und keines davon zeigt das 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());

Außerdem teste ich ADO.NET und habe das gleiche Ergebnis:

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();
}

Ich verwende Visual Studio 2017, .NET Framework 4.6.1 und Microsoft SQL Server Enterprise (64-Bit).

XAMT
quelle
4
Ich bin mir ziemlich sicher, dass es mit den Spracheinstellungen (Maschine gegen Datenbank) zusammenhängt, also haben Sie einfach Glück.
Gert Arnold
2
Ich muss hinzufügen, dass ich und @GertArnold beide im selben Land leben, was eine sehr wahrscheinliche Erklärung dafür ist, warum wir beide das Problem reproduzieren können
Joost K
2
@GertArnold Bitte machen Sie ein Beispiel (VS Solution + SQL Database) und teilen Sie es. Ich werde den Hinweis finden.
XAMT
1
@XAMT Es handelt sich um einen SQL Server Profiler-Fehler, der nicht in unseren Händen liegt. Wenn Sie möchten, können Sie mit den Spracheinstellungen Ihres Computers und des Datenbankservers spielen, um zu sehen, wann der Fehler auftritt, während Sie Ihren Code ausführen. IMO befindet sich jedoch in der Zeitvertreibsabteilung.
Gert Arnold