-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQL Server bulk insert doesn't allow inserting varchar column values longer than 1 character #4399
Comments
Is there a workaround for this while it's getting fixed, e.g. turn off bulk inserts or some other DbContext configuration or usage? |
MaxBatchSize(1) ? |
Thanks, that is a good temporary workaround. For others' benefit, example in
Or configure the options in context class |
@jdlundquist thanks for reporting this. It seems that indeed we are missing including a length when we generate the type names in cc @rowanmiller @AndriySvyryd @ajcvickers This seems very important so I will mark it RC2 right away, but we can still discuss it in triage. |
👍 |
See Issue #4399 The issue was that the update pipeline was always asking the type mapper for the type to use even if a type had been explicitly set for the property. Fixed this and added tests for inserting all mapped types as both single inserts and multiple inserts both with and without Identity columns.
See Issue #4399 The issue was that the update pipeline was always asking the type mapper for the type to use even if a type had been explicitly set for the property. Fixed this and added tests for inserting all mapped types as both single inserts and multiple inserts both with and without Identity columns.
Title
SQL Server bulk insert generates table without specifying varchar column size
Functional impact
For SQL Server, a runtime exception is thrown and you cannot save changes on a
DbContext
when trying to insert more than one of the same type of entity if that entity has an underlyingvarchar
column and you pass a string value longer than 1 character in length.Minimal repro steps
My actual usage scenario involves various tables and entities, but the following is my hypothesis of steps to repro:
varchar(max)
(or perhaps any size greater than 1), e.g.MyTable.ExampleColumn
DbContext
and mapMyTable.ExampleColumn
with.HasColumnType("varchar(max)")
MyTable
entity and put a string value inExampleColumn
longer than 1 characterDbContext.Add
DbContext.SaveChanges()
orDbContext.SaveChangesAsync()
Expected result
Adding multiple entities of the same type to a
DbContext
would insert successfully into the database without throwing an exception, even when they have string column values containing data longer than 1 characterActual result
A runtime exception is thrown:
SqlException: String or binary data would be truncated.
None of the changes to the
DbContext
are saved to the database.Further technical details
I think the starting point for digging into the issue is:
Microsoft.EntityFrameworkCore.Update.Internal.SqlServerUpdateSqlGenerator
. TheGetTypeNameForCopy
method (used byAppendDeclareTable
) should probably include a length for applicable data types (e.g. varchar, nvarchar, etc.) when generating the columns for the temp table.Looking at the code statically without running the EF7 code directly, here's my suggestion for a couple high level things that could be done to make this work:
mapping
is retrieved for the property inGetTypeNameForCopy
. (This may be a separate issue that could be skipped depending on how (2) is done below). Specifically, I think that if you have a varchar(max) column, the mapping currently returned may be the _varchar mapping rather than _varcharmax (fromSqlServerTypeMapper
) due toMicrosoft.EntityFrameworkCore.Storage.FindMapping(IProperty)
implementation that strips off everything after opening parentheses from a column type name. Perhaps this would be OK if there was a fluent API for building the model that allows specifying a SQL server special "Max length" column (e.g. varchar(max) vs. varchar(200)). If the fluent API requires specifyingHasColumnType("varchar(max)")
, then we may need to not strip off the "(max)" from the data type in this case. PerhapsSqlServerTypeMapper
could overloadFindMapping(IProperty)
to customize the logic.mapping.DefaultTypeName
inGetTypeNameForCopy
, get a length-qualified type. The issue RevEng: generated props .HasMaxLength(x).HasColumnType("varchar") create varchar(1) columns in DB #4312 may be related and its proposed fix in PR [RevEng] Output the HasColumnType() and HasMaxLength() APIs correctly #4348 could be useful, e.g. the new methodSqlServerTypeMapper.MaxLengthQualifiedDataType
or something like that. I know there is some custom logic for determining string length sizes for parameters (e.g.SqlServerMaxLengthMapping.ConfigureParameter
discussed in Use column/property facets for parameter types in Update Pipeline #4134 ), so that could be useful to mirror to keep the temp table column types for bulk inserts consistent with the parameter values passed to them.Right now, the generated SQL is something like this (scrubbed actual tables/columns that I got from SQL Server profiler and simplified):
A couple notes:
@toInsert2
columnExampleColumn
needs a length for varchar (either 'max', which is the real column type, or perhaps 8000 to match the parameter) sincevarchar
equates tovarchar(1)
and can't fit any string values larger than 1 character@p0
and@p1
are varchar(8000) even though the DB column is actually varchar(max) and mapped as varchar(max) during 'OnModelCreating` because the runtime parameter values fit within the pre-determined string parameter sizes.The text was updated successfully, but these errors were encountered: