From 690263b0220292c919e708a1b782e7dca0e95558 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 12 Oct 2021 16:26:17 +0200 Subject: [PATCH] Document [Precision] and [Unicode] attributes Closes #2977 Closes #3413 --- .../core/modeling/entity-properties.md | 36 +++++++-- .../core/providers/sql-server/columns.md | 14 +++- .../core/what-is-new/ef-core-6.0/whatsnew.md | 2 +- .../NewInEFCore6/UnicodeAttributeSample.cs | 80 ------------------- .../DataAnnotations/DataAnnotations.csproj | 4 +- .../DataAnnotations/PrecisionAndScale.cs | 21 +++++ .../DataAnnotations/UnicodeAttributeSample.cs | 22 +++++ 7 files changed, 90 insertions(+), 89 deletions(-) delete mode 100644 samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs create mode 100644 samples/core/Modeling/DataAnnotations/PrecisionAndScale.cs create mode 100644 samples/core/Modeling/DataAnnotations/UnicodeAttributeSample.cs diff --git a/entity-framework/core/modeling/entity-properties.md b/entity-framework/core/modeling/entity-properties.md index 5b8a007f16..d8268ebcd7 100644 --- a/entity-framework/core/modeling/entity-properties.md +++ b/entity-framework/core/modeling/entity-properties.md @@ -2,7 +2,7 @@ title: Entity Properties - EF Core description: How to configure and map entity properties using Entity Framework Core author: roji -ms.date: 05/27/2020 +ms.date: 10/12/2021 uid: core/modeling/entity-properties --- # Entity Properties @@ -80,9 +80,7 @@ In the following example, configuring a maximum length of 500 will cause a colum ### Precision and Scale -Starting with EFCore 5.0, you can use fluent API to configure the precision and scale. It tells the database provider how much storage is needed for a given column. It only applies to data types where the provider allows the precision and scale to vary - usually `decimal` and `DateTime`. - -For `decimal` properties, precision defines the maximum number of digits needed to express any value the column will contain, and scale defines the maximum number of decimal places needed. For `DateTime` properties, precision defines the maximum number of digits needed to express fractions of seconds, and scale is not used. +Some relational data types support the precision and scale facets; these control what values can be stored, and how much storage is needed for the column. Which data types support precision and scale is database-dependent, but in most databases `decimal` and `DateTime` types do. For `decimal` properties, precision defines the maximum number of digits needed to express any value the column will contain, and scale defines the maximum number of decimal places needed. For `DateTime` properties, precision defines the maximum number of digits needed to express fractions of seconds, and scale is not used. > [!NOTE] > Entity Framework does not do any validation of precision or scale before passing data to the provider. It is up to the provider or data store to validate as appropriate. For example, when targeting SQL Server, a column of data type `datetime` does not allow the precision to be set, whereas a `datetime2` one can have precision between 0 and 7 inclusive. @@ -91,10 +89,19 @@ In the following example, configuring the `Score` property to have precision 14 #### [Data Annotations](#tab/data-annotations) -Precision and scale cannot currently be configured via data annotations. +> [!NOTE] +> The Data Annotation for configuring precision and scale was introduced in EF Core 6.0. + +[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/PrecisionAndScale.cs?name=PrecisionAndScale&highlight=4,6)] + +> [!NOTE] +> Scale is never defined without first defining precision, so the Data Annotation for defining the scale is `[Precision(precision, scale)]`. #### [Fluent API](#tab/fluent-api) +> [!NOTE] +> The Fluent API for configuring precision and scale was introduced in EF Core 5.0. + [!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/PrecisionAndScale.cs?name=PrecisionAndScale&highlight=3-9)] > [!NOTE] @@ -102,6 +109,25 @@ Precision and scale cannot currently be configured via data annotations. *** +### Unicode + +In some relational databases, different types exist to represent Unicode and non-Unicode text data. For example, in SQL Server, `nvarchar(x)` is used to represent Unicode data in UTF-16, while `varchar(x)` is used to represent non-Unicode data (but see [these notes](xref:core/providers/sql-server/columns#unicode-and-utf-8) on SQL Server UTF-8 support). For databases which don't support this concept, configuring this has no effect. + +Text properties are configured as Unicode by default. You can configure a column as non-Unicode as follows: + +#### [Data Annotations](#tab/data-annotations) + +> [!NOTE] +> The Data Annotation for configuring Unicode was introduced in EF Core 6.0. + +[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Unicode.cs?name=Unicode&highlight=6-7)] + +#### [Fluent API](#tab/fluent-api) + +[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Unicode.cs?name=Unicode&highlight=6-7)] + +*** + ## Required and optional properties A property is considered optional if it is valid for it to contain `null`. If `null` is not a valid value to be assigned to a property then it is considered to be a required property. When mapping to a relational database schema, required properties are created as non-nullable columns, and optional properties are created as nullable columns. diff --git a/entity-framework/core/providers/sql-server/columns.md b/entity-framework/core/providers/sql-server/columns.md index c76ba0cebf..bf5141e2f2 100644 --- a/entity-framework/core/providers/sql-server/columns.md +++ b/entity-framework/core/providers/sql-server/columns.md @@ -2,13 +2,25 @@ title: Microsoft SQL Server Database Provider - Columns - EF Core description: Column features specific to the Entity Framework Core SQL Server provider author: roji -ms.date: 10/1/2021 +ms.date: 10/12/2021 uid: core/providers/sql-server/columns --- # Column features specific to the Entity Framework Core SQL Server provider This page details column configuration options that are specific to the SQL Server provider. +## Unicode and UTF-8 + +SQL Server has two column types for storing textual data: [`nvarchar(x)`](/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql) and [`varchar(x)`](/sql/t-sql/data-types/char-and-varchar-transact-sql); these have traditionally been used to hold Unicode data in the UTF-16 encoding and non-Unicode data, respectively. SQL Server 2019 [introduced](/sql/relational-databases/collations/collation-and-unicode-support#utf8) the ability to store UTF-8 Unicode data in `varchar(x)` columns. + +Unfortunately, this does not currently work out-of-the-box with EF Core's SQL Server provider. To map a string property to a `varchar(x)` column, the Fluent or Data Annotation API is typically used to disable Unicode ([see these docs](xref:core/modeling/entity-properties#unicode)). While this causes the correct column type to be created, it also makes EF Core send database parameters in a way which is incompatible with UTF-8 data: `DbType.AnsiString` is used (signifying non-Unicode data), but `DbType.String` is needed to properly send Unicode data. + +To summarize, follow the following step to store UTF-8 data in SQL Server: + +* Configure the collation for the property with one of SQL Server's UTF-8 collations; these have a `UTF8` suffix ([see the docs on collation](xref:core/modeling/entity-properties##column-collations)). +* Configure the string property as Unicode (the default); this will cause EF Core to create an `nvarchar(x)` column. +* Edit the migrations and manually set the column type to `varchar(x)` intead. + ## Sparse columns > [!NOTE] diff --git a/entity-framework/core/what-is-new/ef-core-6.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-6.0/whatsnew.md index 23e12a78b5..cf71027baf 100644 --- a/entity-framework/core/what-is-new/ef-core-6.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-6.0/whatsnew.md @@ -2476,7 +2476,7 @@ Starting with EF Core 6.0, a string property can now be mapped to a non-Unicode public string Isbn { get; set; } } --> -[!code-csharp[BookEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs?name=BookEntityType)] +[!code-csharp[BookEntityType](../../../../samples/core/Modeling/DataAnnotations/Unicode.cs?name=Unicode&highlight=6-7)] Since ISBNs cannot contain any non-unicode characters, the `Unicode` attribute will cause a non-Unicode string type to be used. In addition, `MaxLength` is used to limit the size of the database column. For example, when using SQL Server, this results in a database column of `varchar(22)`: diff --git a/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs b/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs deleted file mode 100644 index 41658176fd..0000000000 --- a/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; - -public static class UnicodeAttributeSample -{ - public static void Using_UnicodeAttribute() - { - Console.WriteLine($">>>> Sample: {nameof(Using_UnicodeAttribute)}"); - Console.WriteLine(); - - Helpers.RecreateCleanDatabase(); - Helpers.PopulateDatabase(); - - using var context = new BooksContext(); - - var book = context.Books.Single(e => e.Id == 1); - - Console.WriteLine(); - } - - public static class Helpers - { - public static void RecreateCleanDatabase() - { - using var context = new BooksContext(); - - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - } - - public static void PopulateDatabase() - { - using var context = new BooksContext(); - - context.Add( - new Book() { Isbn = "978-0-39-481823-8", Title = "What Do People Do All Day?" }); - - context.SaveChanges(); - } - } - - #region BookEntityType - public class Book - { - public int Id { get; set; } - public string Title { get; set; } - - [Unicode(false)] - [MaxLength(22)] - public string Isbn { get; set; } - } - #endregion - - public class BooksContext : DbContext - { - public DbSet Books { get; set; } - - private readonly bool _quiet; - - public BooksContext(bool quiet = false) - { - _quiet = quiet; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder - .EnableSensitiveDataLogging() - .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCoreSample"); - - if (!_quiet) - { - optionsBuilder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }); - } - } - } -} diff --git a/samples/core/Modeling/DataAnnotations/DataAnnotations.csproj b/samples/core/Modeling/DataAnnotations/DataAnnotations.csproj index bca6293fde..8389c9e52f 100644 --- a/samples/core/Modeling/DataAnnotations/DataAnnotations.csproj +++ b/samples/core/Modeling/DataAnnotations/DataAnnotations.csproj @@ -2,13 +2,13 @@ Exe - net5.0 + net6.0 EFModeling.DataAnnotations EFModeling.DataAnnotations - + diff --git a/samples/core/Modeling/DataAnnotations/PrecisionAndScale.cs b/samples/core/Modeling/DataAnnotations/PrecisionAndScale.cs new file mode 100644 index 0000000000..1fa8699a52 --- /dev/null +++ b/samples/core/Modeling/DataAnnotations/PrecisionAndScale.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.EntityFrameworkCore; + +namespace EFModeling.DataAnnotations.PrecisionAndScale +{ + internal class MyContext : DbContext + { + public DbSet Blogs { get; set; } + } + + #region PrecisionAndScale + public class Blog + { + public int BlogId { get; set; } + [Precision(14, 2)] + public decimal Score { get; set; } + [Precision(3)] + public DateTime LastUpdated { get; set; } + } + #endregion +} diff --git a/samples/core/Modeling/DataAnnotations/UnicodeAttributeSample.cs b/samples/core/Modeling/DataAnnotations/UnicodeAttributeSample.cs new file mode 100644 index 0000000000..244262439a --- /dev/null +++ b/samples/core/Modeling/DataAnnotations/UnicodeAttributeSample.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; + +namespace EFModeling.DataAnnotations.Unicode +{ + internal class MyContext : DbContext + { + public DbSet Books { get; set; } + } + + #region Unicode + public class Book + { + public int Id { get; set; } + public string Title { get; set; } + + [Unicode(false)] + [MaxLength(22)] + public string Isbn { get; set; } + } + #endregion +}