mikependon / RepoDB

A hybrid ORM library for .NET.
Apache License 2.0
1.68k stars 122 forks source link

Bug: Mapping field using quotes fails to insert #1156

Open dyskette opened 9 months ago

dyskette commented 9 months ago

Bug Description

If properties are mapped using quotes like Map("[field]") or FluentMapper.Column("[field]"), then when executing Insert or BulkInsert it throws an exception.

Exception Message on Insert:

Microsoft.Data.SqlClient.SqlException (0x80131904): Must declare the scalar variable "@IdFile".
   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.CompleteAsyncExecuteReader(Boolean isInternal, Boolean forDescribeParameterEncryption)
   at Microsoft.Data.SqlClient.SqlCommand.InternalEndExecuteReader(IAsyncResult asyncResult, Boolean isInternal, String endMethod)
   at Microsoft.Data.SqlClient.SqlCommand.EndExecuteReaderInternal(IAsyncResult asyncResult)
   at Microsoft.Data.SqlClient.SqlCommand.EndExecuteReaderAsync(IAsyncResult asyncResult)
   at Microsoft.Data.SqlClient.SqlCommand.EndExecuteReaderAsyncCallback(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location ---
   at RepoDb.DbConnectionExtension.InsertAsyncInternalBase[TEntity,TResult](IDbConnection connection, String tableName, TEntity entity, IEnumerable`1 fields, String hints, Nullable`1 commandTimeout, String traceKey, IDbTransaction transaction, ITrace trace, IStatementBuilder statementBuilder, CancellationToken cancellationToken) in /var/home/dyskette/Proyectos/github/RepoDB/RepoDb.Core/RepoDb/Operations/DbConnection/Insert.cs:line 772

Exception Message on BulkInsert:

System.MissingFieldException: There are no field(s) found for this operation.
   at RepoDb.SqlConnectionExtension.BulkInsertAsyncInternalBase[TEntity](SqlConnection connection, String tableName, IEnumerable`1 entities, IEnumerable`1 mappings, SqlBulkCopyOptions options, String hints, Nullable`1 bulkCopyTimeout, Nullable`1 batchSize, Nullable`1 isReturnIdentity, Nullable`1 usePhysicalPseudoTempTable, SqlTransaction transaction, CancellationToken cancellationToken) in /var/home/dyskette/Proyectos/github/RepoDB/RepoDb.Extensions/RepoDb.SqlServer.BulkOperations/RepoDb.SqlServer.BulkOperations/Base/BulkInsert.cs:line 500

Basically both errors happen because the comparison between FieldCache.Get result and DbField.Cache result don't match. The former has the quotes I added, the latter fetches the fields without the quotes.

Which means that on Insert, it doesn't remove the identity from the InsertableFields variable here. And on BulkInsert it removes all fields that don't match, which is all of them, here.

In retrospective, I find it silly to be using quotes for the properties now. But, I just copied and pasted the names with quotes as they were generated by the Azure Data Studio and didn't think anything of it. It never crossed my mind that I was doing something wrong there. I was going in circles until I downloaded the source code, added it as references to my project and debugged it. :cry:

Schema and Model:

CREATE TABLE [dbo].[NasFile]
(
    [IdFile] INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
    [FilePath] NVARCHAR(500) NULL,
);

Model that corresponds the schema.

[Map("[dbo].[NasFile]")]
public class BusinessFile
{
    /// <summary>
    /// Id of file
    /// </summary>
    [Map("[IdFile]")]
    public int Id { get; set; }

    /// <summary>
    /// Path of the file in NAS
    /// </summary>
    [Map("[FilePath]")]
    public string? Path { get; set; }
}

Library Version: