diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index c8e3fcce942..140bb017da8 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Reflection; @@ -615,7 +615,7 @@ public static string NoCreateHostBuilder => GetString("NoCreateHostBuilder"); /// - /// Using applicaiton service provider from Microsoft.Extensions.Hosting. + /// Using application service provider from Microsoft.Extensions.Hosting. /// public static string UsingHostingServices => GetString("UsingHostingServices"); diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 4e92a75d4cd..cc751c89ed4 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -1,17 +1,17 @@  - @@ -358,6 +358,6 @@ Change your target project to the migrations project by using the Package Manage No static method 'CreateHostBuilder(string[])' was found on class 'Program'. - Using applicaiton service provider from Microsoft.Extensions.Hosting. + Using application service provider from Microsoft.Extensions.Hosting. \ No newline at end of file diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index a8243461c0d..94c34d5fe97 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -814,20 +814,30 @@ public virtual async ValueTask> AddAsync( /// /// - /// Begins tracking the given entity in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -847,24 +857,30 @@ public virtual EntityEntry Attach([NotNull] TEntity entity) /// /// - /// Begins tracking the given entity in the state such that it will - /// be updated in the database when is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of the entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking the entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -1002,20 +1018,30 @@ public virtual async ValueTask AddAsync( /// /// - /// Begins tracking the given entity in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -1033,24 +1059,30 @@ public virtual EntityEntry Attach([NotNull] object entity) /// /// - /// Begins tracking the given entity in the state such that it will - /// be updated in the database when is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of the entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking the entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -1158,19 +1190,32 @@ public virtual Task AddRangeAsync([NotNull] params object[] entities) /// /// - /// Begins tracking the given entities in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to attach. public virtual void AttachRange([NotNull] params object[] entities) @@ -1182,23 +1227,32 @@ public virtual void AttachRange([NotNull] params object[] entities) /// /// - /// Begins tracking the given entities in the state such that they will - /// be updated in the database when is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of each entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking each entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to update. public virtual void UpdateRange([NotNull] params object[] entities) @@ -1291,19 +1345,32 @@ await SetEntityStateAsync( /// /// - /// Begins tracking the given entities in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to attach. public virtual void AttachRange([NotNull] IEnumerable entities) @@ -1315,23 +1382,32 @@ public virtual void AttachRange([NotNull] IEnumerable entities) /// /// - /// Begins tracking the given entities in the state such that they will - /// be updated in the database when is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of each entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking each entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to update. public virtual void UpdateRange([NotNull] IEnumerable entities) diff --git a/src/EFCore/DbContextOptions.cs b/src/EFCore/DbContextOptions.cs index de6fccaa87f..a760a5e6e61 100644 --- a/src/EFCore/DbContextOptions.cs +++ b/src/EFCore/DbContextOptions.cs @@ -63,11 +63,12 @@ public virtual TExtension GetExtension() } /// - /// Adds the given extension to the options. + /// Adds the given extension to the underlying options and creates a new + /// with the extension added. /// /// The type of extension to be added. /// The extension to be added. - /// The same options instance so that multiple calls can be chained. + /// The new options instance with the given extension added. public abstract DbContextOptions WithExtension([NotNull] TExtension extension) where TExtension : class, IDbContextOptionsExtension; diff --git a/src/EFCore/DbContextOptions`.cs b/src/EFCore/DbContextOptions`.cs index dc27ebaab42..e64e19a0b33 100644 --- a/src/EFCore/DbContextOptions`.cs +++ b/src/EFCore/DbContextOptions`.cs @@ -42,11 +42,12 @@ public DbContextOptions( } /// - /// Adds the given extension to the options. + /// Adds the given extension to the underlying options and creates a new + /// with the extension added. /// /// The type of extension to be added. /// The extension to be added. - /// The same options instance so that multiple calls can be chained. + /// The new options instance with the given extension added. public override DbContextOptions WithExtension(TExtension extension) { Check.NotNull(extension, nameof(extension)); diff --git a/src/EFCore/DbSet.cs b/src/EFCore/DbSet.cs index a6e98f1adbd..e283549126d 100644 --- a/src/EFCore/DbSet.cs +++ b/src/EFCore/DbSet.cs @@ -143,20 +143,30 @@ public virtual ValueTask> AddAsync( /// /// - /// Begins tracking the given entity in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -195,24 +205,30 @@ public virtual ValueTask> AddAsync( /// /// - /// Begins tracking the given entity in the state such that it will - /// be updated in the database when is called. + /// Begins tracking the given entity and entries reachable from the given entity using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of the entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking the entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// /// + /// For entity types without generated keys, the state set is always . + /// + /// /// Use to set the state of only a single entity. /// /// @@ -249,19 +265,32 @@ public virtual ValueTask> AddAsync( /// /// - /// Begins tracking the given entities in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to attach. public virtual void AttachRange([NotNull] params TEntity[] entities) => throw new NotImplementedException(); @@ -287,23 +316,32 @@ public virtual ValueTask> AddAsync( /// /// - /// Begins tracking the given entities in the state such that they will - /// be updated in the database when is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of each entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking each entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to update. public virtual void UpdateRange([NotNull] params TEntity[] entities) => throw new NotImplementedException(); @@ -337,19 +375,32 @@ public virtual Task AddRangeAsync( /// /// - /// Begins tracking the given entities in the state - /// such that no operation will be performed when - /// is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. + /// + /// + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure only new entities will be inserted. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to attach. public virtual void AttachRange([NotNull] IEnumerable entities) => throw new NotImplementedException(); @@ -375,23 +426,32 @@ public virtual Task AddRangeAsync( /// /// - /// Begins tracking the given entities in the state such that they will - /// be updated in the database when is called. + /// Begins tracking the given entities and entries reachable from the given entities using + /// the state by default, but see below for cases + /// when a different state will be used. /// /// - /// All properties of each entity will be marked as modified. To mark only some properties as modified, use - /// to begin tracking each entity in the - /// state and then use the returned to mark the desired properties as modified. + /// Generally, no database interaction will be performed until is called. /// /// /// A recursive search of the navigation properties will be performed to find reachable entities - /// that are not already being tracked by the context. These entities will also begin to be tracked - /// by the context. If a reachable entity has its primary key value set + /// that are not already being tracked by the context. All entities found will be tracked + /// by the context. + /// + /// + /// For entity types with generated keys if an entity has its primary key value set /// then it will be tracked in the state. If the primary key /// value is not set then it will be tracked in the state. + /// This helps ensure new entities will be inserted, while existing entities will be updated. /// An entity is considered to have its primary key value set if the primary key property is set /// to anything other than the CLR default for the property type. /// + /// + /// For entity types without generated keys, the state set is always . + /// + /// + /// Use to set the state of only a single entity. + /// /// /// The entities to update. public virtual void UpdateRange([NotNull] IEnumerable entities) => throw new NotImplementedException(); diff --git a/src/EFCore/Internal/InternalDbSet.cs b/src/EFCore/Internal/InternalDbSet.cs index 18934ec937c..701feb83316 100644 --- a/src/EFCore/Internal/InternalDbSet.cs +++ b/src/EFCore/Internal/InternalDbSet.cs @@ -124,7 +124,12 @@ public override LocalView Local { CheckKey(); - return _localView ?? (_localView = new LocalView(this)); + if (_context.ChangeTracker.AutoDetectChangesEnabled) + { + _context.ChangeTracker.DetectChanges(); + } + + return _localView ??= new LocalView(this); } } diff --git a/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs b/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs index 6c5b5819bdf..9e1707732e8 100644 --- a/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs +++ b/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs @@ -28,8 +28,8 @@ public BoolToStringConverter( [NotNull] string trueValue, [CanBeNull] ConverterMappingHints mappingHints = null) : base( - Check.NotEmpty(falseValue, nameof(falseValue)), - Check.NotEmpty(trueValue, nameof(trueValue)), + Check.NotNull(falseValue, nameof(falseValue)), + Check.NotNull(trueValue, nameof(trueValue)), FromProvider(trueValue), new ConverterMappingHints(size: Math.Max(falseValue.Length, trueValue.Length)).With(mappingHints)) { diff --git a/test/EFCore.Specification.Tests/DatabindingTestBase.cs b/test/EFCore.Specification.Tests/DatabindingTestBase.cs index 07fdaeb19d9..5a42165ad9d 100644 --- a/test/EFCore.Specification.Tests/DatabindingTestBase.cs +++ b/test/EFCore.Specification.Tests/DatabindingTestBase.cs @@ -510,7 +510,7 @@ public virtual void DbSet_Local_is_cached_on_the_set() } [Fact] - public virtual void DbSet_Local_does_not_call_DetectChanges() + public virtual void DbSet_Local_calls_DetectChanges() { using (var context = CreateF1Context()) { @@ -527,7 +527,7 @@ public virtual void DbSet_Local_does_not_call_DetectChanges() context.ChangeTracker.AutoDetectChangesEnabled = false; - Assert.Equal(EntityState.Unchanged, context.Entry(alonso).State); + Assert.Equal(EntityState.Modified, context.Entry(alonso).State); context.ChangeTracker.AutoDetectChangesEnabled = true; diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs index 7d4d13d24e0..18c8f0ce36d 100644 --- a/test/EFCore.Tests/DbContextTest.cs +++ b/test/EFCore.Tests/DbContextTest.cs @@ -41,6 +41,75 @@ public void Set_throws_for_type_not_in_model() } } + [Fact] + public void Local_calls_DetectChanges() + { + var provider = + InMemoryTestHelpers.Instance.CreateServiceProvider( + new ServiceCollection().AddScoped()); + + using (var context = new ButTheHedgehogContext(provider)) + { + var changeDetector = (ChangeDetectorProxy)context.GetService(); + + changeDetector.DetectChangesCalled = false; + + var entry = context.Attach( + new Product + { + Id = 1, + Name = "Little Hedgehogs" + }); + + entry.Entity.Name = "Big Hedgehogs"; + + Assert.False(changeDetector.DetectChangesCalled); + + var _ = context.Set().Local; + + Assert.True(changeDetector.DetectChangesCalled); + Assert.Equal(EntityState.Modified, entry.State); + } + } + + [Fact] + public void Local_does_not_call_DetectChanges_when_disabled() + { + var provider = + InMemoryTestHelpers.Instance.CreateServiceProvider( + new ServiceCollection().AddScoped()); + + using (var context = new ButTheHedgehogContext(provider)) + { + var changeDetector = (ChangeDetectorProxy)context.GetService(); + + context.ChangeTracker.AutoDetectChangesEnabled = false; + + changeDetector.DetectChangesCalled = false; + + var entry = context.Attach( + new Product + { + Id = 1, + Name = "Little Hedgehogs" + }); + + entry.Entity.Name = "Big Hedgehogs"; + + Assert.False(changeDetector.DetectChangesCalled); + + var _ = context.Set().Local; + + Assert.False(changeDetector.DetectChangesCalled); + Assert.Equal(EntityState.Unchanged, entry.State); + + context.ChangeTracker.DetectChanges(); + + Assert.True(changeDetector.DetectChangesCalled); + Assert.Equal(EntityState.Modified, entry.State); + } + } + [Fact] public void Set_throws_for_weak_types() { diff --git a/test/EFCore.Tests/Storage/BoolToStringConverterTest.cs b/test/EFCore.Tests/Storage/BoolToStringConverterTest.cs index 0c672c19e54..c5834286781 100644 --- a/test/EFCore.Tests/Storage/BoolToStringConverterTest.cs +++ b/test/EFCore.Tests/Storage/BoolToStringConverterTest.cs @@ -61,5 +61,25 @@ public void Can_convert_Y_N_strings_to_bool() Assert.False(converter("")); Assert.False(converter(null)); } + + [Fact] + public void Can_convert_bools_to_empty_strings_or_whitespace() + { + var converter = new BoolToStringConverter("", " ").ConvertToProviderExpression.Compile(); + + Assert.Equal(" ", converter(true)); + Assert.Equal("", converter(false)); + } + + [Fact] + public void Can_convert_empty_strings_or_whitespace_to_bool() + { + var converter = new BoolToStringConverter("", " ").ConvertFromProviderExpression.Compile(); + + Assert.False(converter("")); + Assert.True(converter(" ")); + Assert.False(converter("\t")); + Assert.False(converter(null)); + } } }