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 c93af2c02d..2fe2c7c81e 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 @@ -2,15 +2,400 @@ title: What's New in EF Core 6.0 description: Overview of new features in EF Core 6.0 author: ajcvickers -ms.date: 01/12/2021 +ms.date: 01/28/2021 uid: core/what-is-new/ef-core-6.0/whatsnew --- # What's New in EF Core 6.0 -EF Core 6.0 is currently in development. This page will contain an overview of interesting changes introduced in each preview. +EF Core 6.0 is currently in development. This contains an overview of interesting changes introduced in each preview. This page does not duplicate the [plan for EF Core 6.0](xref:core/what-is-new/ef-core-6.0/plan). The plan describes the overall themes for EF Core 6.0, including everything we are planning to include before shipping the final release. +## EF Core 6.0 Preview 1 + +> [!TIP] +> You can run and debug into all the preview 1 samples shown below by [downloading the sample code from GitHub](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Miscellaneous/NewInEFCore6). + +### UnicodeAttribute + +GitHub Issue: [#19794](https://github.com/dotnet/efcore/issues/19794). This feature was contributed by [@RaymondHuy](https://github.com/RaymondHuy). + +Starting with EF Core 6.0, a string property can now be mapped to a non-Unicode column using a mapping attribute _without specifying the database type directly_. For example, consider a `Book` entity type with a property for the [International Standard Book Number (ISBN)](https://en.wikipedia.org/wiki/International_Standard_Book_Number) in the form "ISBN 978-3-16-148410-0": + + +[!code-csharp[BookEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs?name=BookEntityType)] + +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)`: + +```sql +CREATE TABLE [Book] ( + [Id] int NOT NULL IDENTITY, + [Title] nvarchar(max) NULL, + [Isbn] varchar(22) NULL, + CONSTRAINT [PK_Book] PRIMARY KEY ([Id])); +``` + +> [!NOTE] +> EF Core maps string properties to Unicode columns by default. `UnicodeAttribute` is ignored when the database system supports only Unicode types. + +### PrecisionAttribute + +GitHub Issue: [#17914](https://github.com/dotnet/efcore/issues/17914). This feature was contributed by [@RaymondHuy](https://github.com/RaymondHuy). + +The precision and scale of a database column can now be configured using mapping attributes _without specifying the database type directly_. For example, consider a `Product` entity type with a decimal `Price` property: + + +[!code-csharp[ProductEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/PrecisionAttributeSample.cs?name=ProductEntityType)] + +EF Core will map this property to a database column with precision 10 and scale 2. For example, on SQL Server: + +```sql +CREATE TABLE [Product] ( + [Id] int NOT NULL IDENTITY, + [Price] decimal(10,2) NOT NULL, + CONSTRAINT [PK_Product] PRIMARY KEY ([Id])); +``` + +### EntityTypeConfigurationAttribute + +GitHub Issue: [#23163](https://github.com/dotnet/efcore/issues/23163). This feature was contributed by [@KaloyanIT](https://github.com/KaloyanIT). + + instances allow configuration for a each entity type to be contained in its own configuration class. For example: + + +[!code-csharp[BookConfiguration](../../../../samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs?name=BookConfiguration)] + +Normally, this configuration class must be instantiated and called into from . For example: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + new BookConfiguration().Configure(modelBuilder.Entity()); +} +``` + +Starting with EF Core 6.0, an `EntityTypeConfigurationAttribute` can be placed on the entity type such that EF Core can find and use appropriate configuration. For example: + + +[!code-csharp[BookEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs?name=BookEntityType)] + +This attribute means that EF Core will use the specified `IEntityTypeConfiguration` implementation whenever the `Book` entity type is included in a model. The entity type is included in a model using one of the normal mechanisms. For example, by creating a property for the entity type: + + +[!code-csharp[DbContext](../../../../samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs?name=DbContext)] + +Or by registering it in : + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity(); +} +``` + +> [!NOTE] +> `EntityTypeConfigurationAttribute` types will not be automatically discovered in an assembly. Entity types must be added to the model before the attribute will be discovered on that entity type. + +### Translate ToString on SQLite + +GitHub Issue: [#17223](https://github.com/dotnet/efcore/issues/17223). This feature was contributed by [@ralmsdeveloper](https://github.com/ralmsdeveloper). + +Calls to are now translated to SQL when using the SQLite database provider. This can be useful for text searches involving non-string columns. For example, consider a `User` entity type that stores phone numbers as numeric values: + + +[!code-csharp[UserEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/ToStringTranslationSample.cs?name=UserEntityType)] + +`ToString` can be used to convert the number to a string in the database. We can then use this string with a function such as `LIKE` to find numbers that match a pattern. For example, to find all numbers containing 555: + + +[!code-csharp[Query](../../../../samples/core/Miscellaneous/NewInEFCore6/ToStringTranslationSample.cs?name=Query)] + +This translates to the following SQL when using a SQLite database: + +```sql +SELECT COUNT(*) +FROM "Users" AS "u" +WHERE CAST("u"."PhoneNumber" AS TEXT) LIKE '%555%' +``` + +Note that translation of for SQL Server is already supported in EF Core 5.0, and may also be supported by other database providers. + +### EF.Functions.Random + +GitHub Issue: [#16141](https://github.com/dotnet/efcore/issues/16141). This feature was contributed by [@RaymondHuy](https://github.com/RaymondHuy). + +`EF.Functions.Random` maps to a database function returning a pseudo-random number between 0 and 1 exclusive. Translations have been implemented in the EF Core repo for SQL Server, SQLite, and Cosmos. For example, consider a `User` entity type with a `Popularity` property: + + +[!code-csharp[UserEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/RandomFunctionSample.cs?name=UserEntityType)] + +`Popularity` can have values from 1 to 5 inclusive. Using `EF.Functions.Random` we can write a query to return all users with a randomly chosen popularity: + + +[!code-csharp[Query](../../../../samples/core/Miscellaneous/NewInEFCore6/RandomFunctionSample.cs?name=Query)] + +This translates to the following SQL when using a SQL Server database: + +```sql +SELECT [u].[Id], [u].[Popularity], [u].[Username] +FROM [Users] AS [u] +WHERE [u].[Popularity] = (CAST((RAND() * 5.0E0) AS int) + 1) +``` + +### Support for SQL Server sparse columns + +GitHub Issue: [#8023](https://github.com/dotnet/efcore/issues/8023). + +SQL Server [sparse columns](/sql/relational-databases/tables/use-sparse-columns) are ordinary columns that are optimized to store null values. This can be useful when using [TPH inheritance mapping](xref:core/modeling/inheritance) where properties of a rarely used subtype will result in null column values for most rows in the table. For example, consider a `ForumModerator` class that extends from `ForumUser`: + + +[!code-csharp[UserEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/SparseColumnsSample.cs?name=UserEntityType)] + +There may be millions of users, with only a handful of these being moderators. This means mapping the `ForumName` as sparse might make sense here. This can now be configured using `IsSparse` in . For example: + + +[!code-csharp[OnModelCreating](../../../../samples/core/Miscellaneous/NewInEFCore6/SparseColumnsSample.cs?name=OnModelCreating)] + +EF Core migrations will then mark the column as sparse. For example: + +```sql +CREATE TABLE [ForumUser] ( + [Id] int NOT NULL IDENTITY, + [Username] nvarchar(max) NULL, + [Discriminator] nvarchar(max) NOT NULL, + [ForumName] nvarchar(max) SPARSE NULL, + CONSTRAINT [PK_ForumUser] PRIMARY KEY ([Id])); +``` + > [!NOTE] -> Coming soon! +> Sparse columns have limitations. Make sure to read the [SQL Server sparse columns documentation](/sql/relational-databases/tables/use-sparse-columns) to ensure that sparse columns are the right choice for your scenario. + +### In-memory database: validate required properties are not null + +GitHub Issue: [#10613](https://github.com/dotnet/efcore/issues/10613). This feature was contributed by [@fagnercarvalho](https://github.com/fagnercarvalho). + +The EF Core in-memory database will now throw an exception if an attempt is made to save a null value for a property marked as required. For example, consider a `User` type with a required `Username` property: + + +[!code-csharp[UserEntityType](../../../../samples/core/Miscellaneous/NewInEFCore6/InMemoryRequiredPropertiesSample.cs?name=UserEntityType)] + +Attempting to save an entity with a null `Username` will result in the following exception: + +> Microsoft.EntityFrameworkCore.DbUpdateException: Required properties '{'Username'}' are missing for the instance of entity type 'User' with the key value '{Id: 1}'. + +This validation can be disabled if necessary. For example: + + +[!code-csharp[OnConfiguring](../../../../samples/core/Miscellaneous/NewInEFCore6/InMemoryRequiredPropertiesSample.cs?name=OnConfiguring)] + +### Improved SQL Server translation for IsNullOrWhitespace + +GitHub Issue: [#22916](https://github.com/dotnet/efcore/issues/22916). This feature was contributed by [@Marusyk](https://github.com/Marusyk). + +Consider the following query: + + +[!code-csharp[Query](../../../../samples/core/Miscellaneous/NewInEFCore6/IsNullOrWhitespaceSample.cs?name=Query)] + +Before EF Core 6.0, this was translated to the following on SQL Server: + +```sql +SELECT [u].[Id], [u].[FirstName], [u].[LastName] +FROM [Users] AS [u] +WHERE ([u].[FirstName] IS NULL OR (LTRIM(RTRIM([u].[FirstName])) = N'')) OR ([u].[LastName] IS NULL OR (LTRIM(RTRIM([u].[LastName])) = N'')) +``` + +This translation has been improved for EF Core 6.0 to: + +```sql +SELECT [u].[Id], [u].[FirstName], [u].[LastName] +FROM [Users] AS [u] +WHERE ([u].[FirstName] IS NULL OR ([u].[FirstName] = N'')) OR ([u].[LastName] IS NULL OR ([u].[LastName] = N'')) +``` + +### Database comments are scaffolded to code comments + +GitHub Issue: [#19113](https://github.com/dotnet/efcore/issues/19113). This feature was contributed by [@ErikEJ](https://github.com/ErikEJ). + +Comments on SQL tables and columns are now scaffolded into the entity types created when [reverse-engineering an EF Core model](xref:core/managing-schemas/scaffolding) from an existing SQL Server database. For example: + +```csharp +/// +/// The Blog table. +/// +public partial class Blog +{ + /// + /// The primary key. + /// + [Key] + public int Id { get; set; } +} +``` + +## Microsoft.Data.Sqlite 6.0 Preview 1 + +> [!TIP] +> You can run and debug into all the preview 1 samples shown below by [downloading the sample code from GitHub](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Miscellaneous/NewInEFCore6). + +### Savepoints API + +GitHub Issue: [#20228](https://github.com/dotnet/efcore/issues/20228). + +We have been standardizing on [a common API for savepoints in ADO.NET providers](https://github.com/dotnet/runtime/issues/33397). Microsoft.Data.Sqlite now supports this API, including: + +- to create a savepoint in the transaction +- to roll back to a previous savepoint +- to release a savepoint + +Using a savepoint allows part of a transaction to be rolled back without rolling back the entire transaction. For example, the code below: + +- Creates a transaction +- Sends an update to the database +- Creates a savepoint +- Sends another update to the database +- Rolls back to the savepoint previous created +- Commits the transaction + + +[!code-csharp[PerformUpdates](../../../../samples/core/Miscellaneous/NewInEFCore6/SqliteSamples.cs?name=PerformUpdates)] + +This will result in the first update being committed to the database, while the second update is not committed since the savepoint was rolled back before committing the transaction. + +### Command timeout in the connection string + +GitHub Issue: [#22505](https://github.com/dotnet/efcore/issues/22505). This feature was contributed by [@nmichels](https://github.com/nmichels). + +ADO.NET providers support two distinct timeouts: + +- The connection timeout, which determines the maximum time to wait when making a connection to the database. +- The command timeout, which determines the maximum time to wait for a command to complete executing. + +The command timeout can be set from code using . Many providers are now also exposing this command timeout in the connection string. Microsoft.Data.Sqlite is following this trend with the `Command Timeout` connection string keyword. For example, `"Command Timeout=60;DataSource=test.db"` will use 60 seconds as the default timeout for commands created by the connection. + +> [!TIP] +> Sqlite treats `Default Timeout` as a synonym for `Command Timeout` and so can be used instead if preferred. diff --git a/samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs b/samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs new file mode 100644 index 0000000000..c0d1e6d30d --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/EntityTypeConfigurationAttributeSample.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +public static class EntityTypeConfigurationAttributeSample +{ + public static void Using_EntityTypeConfigurationAttribute() + { + Console.WriteLine($">>>> Sample: {nameof(Using_EntityTypeConfigurationAttribute)}"); + 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 + [EntityTypeConfiguration(typeof(BookConfiguration))] + public class Book + { + public int Id { get; set; } + public string Title { get; set; } + public string Isbn { get; set; } + } + #endregion + + #region BookConfiguration + public class BookConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(e => e.Isbn) + .IsUnicode(false) + .HasMaxLength(22); + } + } + #endregion + + #region DbContext + public class BooksContext : DbContext + { + public DbSet Books { get; set; } + + //... + #endregion + + 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/Miscellaneous/NewInEFCore6/InMemoryRequiredPropertiesSample.cs b/samples/core/Miscellaneous/NewInEFCore6/InMemoryRequiredPropertiesSample.cs new file mode 100644 index 0000000000..5d23927a8b --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/InMemoryRequiredPropertiesSample.cs @@ -0,0 +1,81 @@ + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class InMemoryRequiredPropertiesSample +{ + public static void Required_properties_validated_with_in_memory_database() + { + Console.WriteLine($">>>> Sample: {nameof(Required_properties_validated_with_in_memory_database)}"); + Console.WriteLine(); + + using var context = new UserContext(); + + context.Add(new User()); + + try + { + context.SaveChanges(); + } + catch (Exception e) + { + Console.WriteLine($"{e.GetType().FullName}: {e.Message}"); + } + + Console.WriteLine(); + } + + public static void Required_property_validation_with_in_memory_database_can_be_disabled() + { + Console.WriteLine($">>>> Sample: {nameof(Required_property_validation_with_in_memory_database_can_be_disabled)}"); + Console.WriteLine(); + + using var context = new UserContextWithNullCheckingDisabled(); + + context.Add(new User()); + + context.SaveChanges(); + + Console.WriteLine(); + } + + #region UserEntityType + public class User + { + public int Id { get; set; } + + [Required] + public string Username { get; set; } + } + #endregion + + public class UserContext : DbContext + { + public DbSet Users { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .LogTo(Console.WriteLine, new[] { InMemoryEventId.ChangesSaved }) + .EnableSensitiveDataLogging() + .UseInMemoryDatabase("UserContext"); + } + } + + public class UserContextWithNullCheckingDisabled : DbContext + { + public DbSet Users { get; set; } + + #region OnConfiguring + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .LogTo(Console.WriteLine, new[] { InMemoryEventId.ChangesSaved }) + .UseInMemoryDatabase("UserContextWithNullCheckingDisabled") + .EnableNullabilityCheck(false); + } + #endregion + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore6/IsNullOrWhitespaceSample.cs b/samples/core/Miscellaneous/NewInEFCore6/IsNullOrWhitespaceSample.cs new file mode 100644 index 0000000000..28e65299fa --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/IsNullOrWhitespaceSample.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class IsNullOrWhitespaceSample +{ + public static void Translate_IsNullOrWhitespace() + { + Console.WriteLine($">>>> Sample: {nameof(Translate_IsNullOrWhitespace)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + #region Query + var users = context.Users.Where( + e => string.IsNullOrWhiteSpace(e.FirstName) + || string.IsNullOrWhiteSpace(e.LastName)).ToList(); + #endregion + + foreach (var user in users) + { + Console.WriteLine($"Found '{user.FirstName}' '{user.LastName}'"); + } + + Console.WriteLine(); + } + + public static class Helpers + { + public static void RecreateCleanDatabase() + { + using var context = new BooksContext(quiet: true); + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + public static void PopulateDatabase() + { + using var context = new BooksContext(quiet: true); + + context.AddRange( + new User + { + FirstName = "Arthur" + }, + new User + { + FirstName = "Arthur", + LastName = "Vickers" + }, + new User + { + FirstName = "", + LastName = "Vickers" + }, + new User + { + FirstName = " ", + LastName = "Vickers" + }, + new User + { + FirstName = "Arthur", + LastName = "\t" + }); + + context.SaveChanges(); + } + } + + #region ProductEntityType + public class User + { + public int Id { get; set; } + + public string FirstName { get; set; } + public string LastName { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Users { 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/Miscellaneous/NewInEFCore6/NewInEFCore6.csproj b/samples/core/Miscellaneous/NewInEFCore6/NewInEFCore6.csproj new file mode 100644 index 0000000000..8b7d926440 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/NewInEFCore6.csproj @@ -0,0 +1,16 @@ + + + + Exe + net5.0 + + + + + + + + + + + diff --git a/samples/core/Miscellaneous/NewInEFCore6/PrecisionAttributeSample.cs b/samples/core/Miscellaneous/NewInEFCore6/PrecisionAttributeSample.cs new file mode 100644 index 0000000000..2d9e953987 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/PrecisionAttributeSample.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class PrecisionAttributeSample +{ + public static void Using_PrecisionAttribute() + { + Console.WriteLine($">>>> Sample: {nameof(Using_PrecisionAttribute)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + _ = context.Products.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 Product { Price = 3.99m }); + + context.SaveChanges(); + } + } + + #region ProductEntityType + public class Product + { + public int Id { get; set; } + + [Precision(precision: 10, scale: 2)] + public decimal Price { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Products { 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/Miscellaneous/NewInEFCore6/Program.cs b/samples/core/Miscellaneous/NewInEFCore6/Program.cs new file mode 100644 index 0000000000..c01ef8e9c0 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/Program.cs @@ -0,0 +1,24 @@ +using System; + +public class Program +{ + public static void Main() + { + Console.WriteLine("Samples for _Accessing Tracked Entities_"); + Console.WriteLine(); + + EntityTypeConfigurationAttributeSample.Using_EntityTypeConfigurationAttribute(); + UnicodeAttributeSample.Using_UnicodeAttribute(); + PrecisionAttributeSample.Using_PrecisionAttribute(); + ToStringTranslationSample.Using_ToString_in_queries(); + RandomFunctionSample.Call_EF_Functions_Random(); + SparseColumnsSample.Use_sparse_columns(); + InMemoryRequiredPropertiesSample.Required_properties_validated_with_in_memory_database(); + InMemoryRequiredPropertiesSample.Required_property_validation_with_in_memory_database_can_be_disabled(); + SqliteSamples.SavepointsApi(); + IsNullOrWhitespaceSample.Translate_IsNullOrWhitespace(); + + // Did not make it for preview 1 + // StringConcatSample.Concat_with_multiple_args(); + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore6/RandomFunctionSample.cs b/samples/core/Miscellaneous/NewInEFCore6/RandomFunctionSample.cs new file mode 100644 index 0000000000..931ab1f3b1 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/RandomFunctionSample.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class RandomFunctionSample +{ + public static void Call_EF_Functions_Random() + { + Console.WriteLine($">>>> Sample: {nameof(Call_EF_Functions_Random)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + #region Query + var users = context.Users.Where(u => u.Popularity == (int)(EF.Functions.Random() * 5.0) + 1).ToList(); + #endregion + + foreach (var user in users) + { + Console.WriteLine($" Found '{user.Username}' with popularity '{user.Popularity}'"); + } + + Console.WriteLine(); + } + + public static class Helpers + { + public static void RecreateCleanDatabase() + { + using var context = new BooksContext(quiet: true); + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + public static void PopulateDatabase() + { + using var context = new BooksContext(quiet: true); + + context.AddRange( + new User { Popularity = 1, Username = "arthur" }, + new User { Popularity = 2, Username = "wendy" }, + new User { Popularity = 3, Username = "smokey" }, + new User { Popularity = 4, Username = "alice" }, + new User { Popularity = 5, Username = "mac" }, + new User { Popularity = 1, Username = "baxter" }, + new User { Popularity = 2, Username = "olive" }, + new User { Popularity = 3, Username = "toast" }); + + context.SaveChanges(); + } + } + + #region UserEntityType + public class User + { + public int Id { get; set; } + public string Username { get; set; } + public int Popularity { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Users { 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/Miscellaneous/NewInEFCore6/SparseColumnsSample.cs b/samples/core/Miscellaneous/NewInEFCore6/SparseColumnsSample.cs new file mode 100644 index 0000000000..31947e78ce --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/SparseColumnsSample.cs @@ -0,0 +1,99 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class SparseColumnsSample +{ + public static void Use_sparse_columns() + { + Console.WriteLine($">>>> Sample: {nameof(Use_sparse_columns)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + #region Query + _ = context.Users.ToList(); + #endregion + + 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.AddRange( + new ForumUser { Username = "arthur" }, + new ForumUser { Username = "wendy" }, + new ForumUser { Username = "smokey" }, + new ForumUser { Username = "alice" }, + new ForumUser { Username = "mac" }, + new ForumModerator { Username = "baxter", ForumName = "Viral Cats"}, + new ForumUser { Username = "olive" }, + new ForumUser { Username = "toast" }); + + context.SaveChanges(); + } + } + + #region UserEntityType + public class ForumUser + { + public int Id { get; set; } + public string Username { get; set; } + } + + public class ForumModerator : ForumUser + { + public string ForumName { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Users { 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 }); + } + } + + #region OnModelCreating + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .Entity() + .Property(e => e.ForumName) + .IsSparse(); + } + #endregion + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore6/SqliteSamples.cs b/samples/core/Miscellaneous/NewInEFCore6/SqliteSamples.cs new file mode 100644 index 0000000000..e67d08d845 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/SqliteSamples.cs @@ -0,0 +1,126 @@ +using System; +using System.Linq; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class SqliteSamples +{ + public static void SavepointsApi() + { + Console.WriteLine($">>>> Sample: {nameof(SavepointsApi)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + Console.WriteLine("After update:"); + + using (var context = new BooksContext()) + { + foreach (var user in context.Users) + { + Console.WriteLine($" Found '{user.Username}'"); + } + } + + PerformUpdates(); + + Console.WriteLine(); + Console.WriteLine("After update:"); + + using (var context = new BooksContext()) + { + foreach (var user in context.Users) + { + Console.WriteLine($" Found '{user.Username}'"); + } + } + } + + private static void PerformUpdates() + { + #region PerformUpdates + using var connection = new SqliteConnection("DataSource=test.db"); + connection.Open(); + + using var transaction = connection.BeginTransaction(); + + using (var command = connection.CreateCommand()) + { + command.CommandText = @"UPDATE Users SET Username = 'ajcvickers' WHERE Id = 1"; + command.ExecuteNonQuery(); + } + + transaction.Save("MySavepoint"); + + using (var command = connection.CreateCommand()) + { + command.CommandText = @"UPDATE Users SET Username = 'wfvickers' WHERE Id = 2"; + command.ExecuteNonQuery(); + } + + transaction.Rollback("MySavepoint"); + + transaction.Commit(); + #endregion + } + + public static class Helpers + { + public static void RecreateCleanDatabase() + { + using var context = new BooksContext(quiet: true); + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + public static void PopulateDatabase() + { + using var context = new BooksContext(quiet: true); + + context.AddRange( + new User { Username = "arthur" }, + new User { Username = "wendy" }, + new User { Username = "microsoft" }); + + context.SaveChanges(); + + context.ChangeTracker.Entries().First().Entity.Username = "ajcvickers"; + context.SaveChanges(); + } + } + + #region UserEntityType + public class User + { + public int Id { get; set; } + public string Username { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Users { get; set; } + + private readonly bool _quiet; + + public BooksContext(bool quiet = false) + { + _quiet = quiet; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableSensitiveDataLogging() + .UseSqlite("Command Timeout=60;DataSource=test.db"); + + if (!_quiet) + { + optionsBuilder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }); + } + } + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore6/StringConcatSample.cs b/samples/core/Miscellaneous/NewInEFCore6/StringConcatSample.cs new file mode 100644 index 0000000000..4b5d4881e3 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/StringConcatSample.cs @@ -0,0 +1,118 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class StringConcatSample +{ + public static void Concat_with_multiple_args() + { + Console.WriteLine($">>>> Sample: {nameof(Concat_with_multiple_args)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + var shards = context.Shards.Where(e => string.Concat(e.Token1, e.Token2, e.Token3) != e.TokensProcessed).ToList(); + + foreach (var shard in shards) + { + Console.WriteLine($"Found shard {shard.Id} with unprocessed tokens."); + } + + Console.WriteLine(); + } + + public static class Helpers + { + public static void RecreateCleanDatabase() + { + using var context = new BooksContext(quiet: true); + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + public static void PopulateDatabase() + { + using var context = new BooksContext(quiet: true); + + context.AddRange( + new Shard + { + Token1 = "A", + Token2 = "B", + Token3 = "C", + TokensProcessed = "ABC" + }, + new Shard + { + Token1 = "D", + Token2 = "H", + Token3 = "C", + TokensProcessed = "DH" + }, + new Shard + { + Token1 = "A", + Token2 = "B", + Token3 = "C" + }, + new Shard + { + Token1 = "D", + Token2 = "E", + Token3 = "F", + TokensProcessed = "DEF" + }, + new Shard + { + Token1 = "J", + Token2 = "A", + Token3 = "M", + TokensProcessed = "JAM" + }); + + context.SaveChanges(); + } + } + + #region ProductEntityType + public class Shard + { + public int Id { get; set; } + + public string Token1 { get; set; } + public string Token2 { get; set; } + public string Token3 { get; set; } + + public string TokensProcessed { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Shards { 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/Miscellaneous/NewInEFCore6/ToStringTranslationSample.cs b/samples/core/Miscellaneous/NewInEFCore6/ToStringTranslationSample.cs new file mode 100644 index 0000000000..91f7313ffe --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/ToStringTranslationSample.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +public static class ToStringTranslationSample +{ + public static void Using_ToString_in_queries() + { + Console.WriteLine($">>>> Sample: {nameof(Using_ToString_in_queries)}"); + Console.WriteLine(); + + Helpers.RecreateCleanDatabase(); + Helpers.PopulateDatabase(); + + using var context = new BooksContext(); + + #region Query + var users = context.Users.Where(u => EF.Functions.Like(u.PhoneNumber.ToString(), "%555%")).ToList(); + #endregion + + foreach (var user in users) + { + Console.WriteLine($" Found '{user.Username}' with phone number '{user.PhoneNumber}'"); + } + + Console.WriteLine(); + } + + public static class Helpers + { + public static void RecreateCleanDatabase() + { + using var context = new BooksContext(quiet: true); + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + public static void PopulateDatabase() + { + using var context = new BooksContext(quiet: true); + + context.AddRange( + new User { PhoneNumber = 4255551234, Username = "arthur" }, + new User { PhoneNumber = 5155552234, Username = "wendy" }, + new User { PhoneNumber = 18005525123, Username = "microsoft" }); + + context.SaveChanges(); + } + } + + #region UserEntityType + public class User + { + public int Id { get; set; } + public string Username { get; set; } + public long PhoneNumber { get; set; } + } + #endregion + + public class BooksContext : DbContext + { + public DbSet Users { get; set; } + + private readonly bool _quiet; + + public BooksContext(bool quiet = false) + { + _quiet = quiet; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableSensitiveDataLogging() + .UseSqlite("DataSource=test.db"); + + if (!_quiet) + { + optionsBuilder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }); + } + } + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs b/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs new file mode 100644 index 0000000000..41658176fd --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore6/UnicodeAttributeSample.cs @@ -0,0 +1,80 @@ +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/Samples.sln b/samples/core/Samples.sln index 10a27ea9e5..6d1115f1f5 100644 --- a/samples/core/Samples.sln +++ b/samples/core/Samples.sln @@ -153,6 +153,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NullSemantics", "Querying\N EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CascadeDeletes", "CascadeDeletes\CascadeDeletes.csproj", "{1F72C1AD-D6E1-4F06-B0C7-B04B57DA22B6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewInEFCore6", "Miscellaneous\NewInEFCore6\NewInEFCore6.csproj", "{DADCEB89-6242-4A40-A390-0D957E02D426}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -419,6 +421,10 @@ Global {1F72C1AD-D6E1-4F06-B0C7-B04B57DA22B6}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F72C1AD-D6E1-4F06-B0C7-B04B57DA22B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F72C1AD-D6E1-4F06-B0C7-B04B57DA22B6}.Release|Any CPU.Build.0 = Release|Any CPU + {DADCEB89-6242-4A40-A390-0D957E02D426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DADCEB89-6242-4A40-A390-0D957E02D426}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DADCEB89-6242-4A40-A390-0D957E02D426}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DADCEB89-6242-4A40-A390-0D957E02D426}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -482,6 +488,7 @@ Global {B696F0AC-5282-4E95-9043-A177E3A9C5C0} = {F55D545B-2B45-47A5-8C55-39F972528400} {D1AB173F-582D-43C3-9EC0-20CE9FECFEB6} = {F55D545B-2B45-47A5-8C55-39F972528400} {A241ED91-DE41-4310-B7CD-802F4304F27D} = {1AD64707-0BE0-48B0-A803-916FF96DCB4F} + {DADCEB89-6242-4A40-A390-0D957E02D426} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {20C98D35-54EF-46A6-8F3B-1855C1AE4F70}