From 3bcfc72cbcb66f8393e33d920cc4732ebc100ebc Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 28 Dec 2017 13:59:56 -0800 Subject: [PATCH] Initial implementation of lazy-loading and entities with constructors Parts of issues #3342, #240, #10509, #3797 The main things here are: - Support for injecting values into parameterized entity constructors - Property values are injected if the parameter type and name matches - The current DbContext as DbContext or a derived DbContext type - A service from the internal or external service provider - A delegate to a method of a service - The IEntityType for the entity - Use of the above to inject lazy loading capabilities into entities For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below. Currently all constructor injection is done by convention. Remaining work includes: - API/attributes to configure the constructor binding - Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.) - Allow property injection for services - Configuration of which entities/properties should be lazy loaded and which should not ### Examples In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.) ```C# public class Blog { private int _blogId; // This constructor used by EF Core private Blog( int blogId, string title, int? monthlyRevenue) { _blogId = blogId; Title = title; MonthlyRevenue = monthlyRevenue; } public Blog( string title, int? monthlyRevenue = null) : this(0, title, monthlyRevenue) { } public string Title { get; } public int? MonthlyRevenue { get; set; } } ``` In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself. ```C# public class LazyBlog { private readonly ILazyLoader _loader; private ICollection _lazyPosts = new List(); public LazyBlog() { } private LazyBlog(ILazyLoader loader) { _loader = loader; } public int Id { get; set; } public ICollection LazyPosts => _loader.Load(this, ref _lazyPosts); } public class LazyPost { private readonly ILazyLoader _loader; private LazyBlog _lazyBlog; public LazyPost() { } private LazyPost(ILazyLoader loader) { _loader = loader; } public int Id { get; set; } public LazyBlog LazyBlog { get => _loader.Load(this, ref _lazyBlog); set => _lazyBlog = value; } } ``` This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate. ```C# public class LazyPocoBlog { private readonly Action _loader; private ICollection _lazyPocoPosts = new List(); public LazyPocoBlog() { } private LazyPocoBlog(Action lazyLoader) { _loader = lazyLoader; } public int Id { get; set; } public ICollection LazyPocoPosts => _loader.Load(this, ref _lazyPocoPosts); } public class LazyPocoPost { private readonly Action _loader; private LazyPocoBlog _lazyPocoBlog; public LazyPocoPost() { } private LazyPocoPost(Action lazyLoader) { _loader = lazyLoader; } public int Id { get; set; } public LazyPocoBlog LazyPocoBlog { get => _loader.Load(this, ref _lazyPocoBlog); set => _lazyPocoBlog = value; } } public static class TestPocoLoadingExtensions { public static TRelated Load( this Action loader, object entity, ref TRelated navigationField, [CallerMemberName] string navigationName = null) where TRelated : class { loader?.Invoke(entity, navigationName); return navigationField; } } ``` --- .../Conventions/OracleConventionSetBuilder.cs | 5 +- .../WithConstructorsOracleTest.cs | 40 + .../Design/CSharpSnapshotGenerator.cs | 3 +- .../Query/Internal/IMaterializerFactory.cs | 2 +- .../Internal/InMemoryQueryModelVisitor.cs | 5 +- .../Query/Internal/MaterializerFactory.cs | 21 +- .../Internal/BufferedEntityShaper`.cs | 4 +- .../Internal/BufferedOffsetEntityShaper.cs | 2 +- .../Internal/EntityShaper.cs | 4 +- .../Internal/IMaterializerFactory.cs | 2 +- .../Internal/MaterializerFactory.cs | 21 +- .../Internal/UnbufferedEntityShaper.cs | 4 +- .../Internal/UnbufferedOffsetEntityShaper.cs | 2 +- ...ationalEntityQueryableExpressionVisitor.cs | 2 +- .../DatabindingTestBase.cs | 6 +- .../FieldMappingTestBase.cs | 292 ++-- .../GraphUpdatesFixtureBase.cs | 612 +++---- .../LoadTestBase.cs | 1501 ++++++++++++++++- .../OptimisticConcurrencyTestBase.cs | 14 +- .../TestModels/ConcurrencyModel/Chassis.cs | 26 +- .../TestModels/ConcurrencyModel/Driver.cs | 42 +- .../TestModels/ConcurrencyModel/Engine.cs | 40 +- .../ConcurrencyModel/EngineSupplier.cs | 21 +- .../TestModels/ConcurrencyModel/Gearbox.cs | 10 + .../TestModels/ConcurrencyModel/Location.cs | 11 +- .../ConcurrencyModel/SponsorDetails.cs | 10 + .../TestModels/ConcurrencyModel/Team.cs | 67 +- .../TestModels/ConcurrencyModel/TestDriver.cs | 19 + .../ConcurrencyModel/TitleSponsor.cs | 18 +- .../TestPocoLoadingExtensions.cs | 23 + .../WithConstructorsTestBase.cs | 400 +++++ .../SqlServerConventionSetBuilder.cs | 5 +- .../Conventions/SqliteConventionSetBuilder.cs | 6 +- src/EFCore/ChangeTracking/ChangeTracker.cs | 12 + .../Internal/ArrayPropertyValues.cs | 2 +- .../ChangeTracking/Internal/StateManager.cs | 8 +- src/EFCore/ChangeTracking/LocalView.cs | 22 +- src/EFCore/DbContext.cs | 7 +- src/EFCore/Diagnostics/CoreEventId.cs | 30 +- .../Diagnostics/LazyLoadingEventData.cs | 45 + .../Internal/CoreLoggerExtensions.cs | 76 + src/EFCore/Extensions/ModelExtensions.cs | 21 +- src/EFCore/ILazyLoader.cs | 23 + .../Infrastructure/CoreOptionsExtension.cs | 3 +- .../EntityFrameworkServicesBuilder.cs | 4 + .../ModelCustomizerDependencies.cs | 1 - src/EFCore/Infrastructure/ModelValidator.cs | 7 +- src/EFCore/Internal/InternalDbSet.cs | 5 +- src/EFCore/Internal/LazyLoader.cs | 70 + src/EFCore/LazyLoaderExtensions.cs | 40 + .../Internal/ConstructorBindingConvention.cs | 74 + .../Internal/CoreConventionSetBuilder.cs | 1 + .../CoreConventionSetBuilderDependencies.cs | 23 +- .../Metadata/Internal/ConstructorBinding.cs | 38 + .../Internal/ConstructorBindingFactory.cs | 55 + .../Internal/ContextParameterBinding.cs | 39 + .../ContextParameterBindingFactory.cs | 23 + .../Metadata/Internal/CoreAnnotationNames.cs | 6 + .../Internal/DirectConstructorBinding.cs | 45 + .../Internal/EntityMaterializerSource.cs | 54 +- .../Internal/EntityTypeParameterBinding.cs | 30 + .../EntityTypeParameterBindingFactory.cs | 23 + .../FactoryMethodConstructorBinding.cs | 73 + .../Internal/IConstructorBindingFactory.cs | 26 + .../Internal/IEntityMaterializerSource.cs | 3 +- .../LazyLoaderParameterBindingFactory.cs | 49 + .../Metadata/Internal/ParameterBinding.cs | 36 + ...rializer.cs => ParameterBindingFactory.cs} | 9 +- .../Metadata/Internal/ParameterBindingInfo.cs | 58 + .../Internal/PropertyBaseExtensions.cs | 22 +- .../Internal/PropertyParameterBinding.cs | 35 + .../PropertyParameterBindingFactory.cs | 48 + .../Internal/ServiceMethodParameterBinding.cs | 62 + .../Internal/ServiceParameterBinding.cs | 46 + src/EFCore/Properties/CoreStrings.Designer.cs | 42 +- src/EFCore/Properties/CoreStrings.resx | 16 +- src/EFCore/Query/EntityLoadInfo.cs | 30 +- .../Design/CSharpMigrationsGeneratorTest.cs | 7 +- .../Query/WarningsTest.cs | 115 +- .../WithConstructorsInMemoryTest.cs | 31 + .../Internal/DiscriminatorConventionTest.cs | 5 +- .../TestRelationalConventionSetBuilder.cs | 4 +- .../LoadSqlServerTest.cs | 396 +++++ .../WithConstructorsSqlServerTest.cs | 25 + .../SqlServerModelValidatorTest.cs | 16 +- .../WithConstructorsSqliteTest.cs | 25 + .../SqliteMigrationAnnotationProviderTest.cs | 4 +- .../SqliteModelValidatorTest.cs | 8 +- .../ChangeTracking/ChangeTrackerTest.cs | 10 +- .../Internal/StateManagerTest.cs | 17 +- test/EFCore.Tests/DbContextTest.cs | 10 +- .../Conventions/ConventionSetBuilderTests.cs | 4 +- .../Internal/CascadeDeleteConventionTest.cs | 5 +- .../ConstructorBindingConventionTest.cs | 375 ++++ .../EntityTypeAttributeConventionTest.cs | 4 +- .../Internal/ForeignKeyIndexConventionTest.cs | 5 +- .../NavigationAttributeConventionTest.cs | 15 +- .../PropertyAttributeConventionTest.cs | 32 +- .../Internal/EntityMaterializerSourceTest.cs | 226 ++- test/EFCore.Tests/ModelSourceTest.cs | 11 +- 100 files changed, 5177 insertions(+), 755 deletions(-) create mode 100644 samples/OracleProvider/test/OracleProvider.FunctionalTests/WithConstructorsOracleTest.cs create mode 100644 src/EFCore.Specification.Tests/TestUtilities/TestPocoLoadingExtensions.cs create mode 100644 src/EFCore.Specification.Tests/WithConstructorsTestBase.cs create mode 100644 src/EFCore/Diagnostics/LazyLoadingEventData.cs create mode 100644 src/EFCore/ILazyLoader.cs create mode 100644 src/EFCore/Internal/LazyLoader.cs create mode 100644 src/EFCore/LazyLoaderExtensions.cs create mode 100644 src/EFCore/Metadata/Conventions/Internal/ConstructorBindingConvention.cs create mode 100644 src/EFCore/Metadata/Internal/ConstructorBinding.cs create mode 100644 src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/ContextParameterBinding.cs create mode 100644 src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/DirectConstructorBinding.cs create mode 100644 src/EFCore/Metadata/Internal/EntityTypeParameterBinding.cs create mode 100644 src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/FactoryMethodConstructorBinding.cs create mode 100644 src/EFCore/Metadata/Internal/IConstructorBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/LazyLoaderParameterBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/ParameterBinding.cs rename src/EFCore/Metadata/Internal/{IEntityMaterializer.cs => ParameterBindingFactory.cs} (73%) create mode 100644 src/EFCore/Metadata/Internal/ParameterBindingInfo.cs create mode 100644 src/EFCore/Metadata/Internal/PropertyParameterBinding.cs create mode 100644 src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs create mode 100644 src/EFCore/Metadata/Internal/ServiceMethodParameterBinding.cs create mode 100644 src/EFCore/Metadata/Internal/ServiceParameterBinding.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/WithConstructorsSqlServerTest.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/WithConstructorsSqliteTest.cs create mode 100644 test/EFCore.Tests/Metadata/Conventions/Internal/ConstructorBindingConventionTest.cs diff --git a/samples/OracleProvider/src/OracleProvider/Metadata/Conventions/OracleConventionSetBuilder.cs b/samples/OracleProvider/src/OracleProvider/Metadata/Conventions/OracleConventionSetBuilder.cs index 3e0be27ad30..5a1dd637fd8 100644 --- a/samples/OracleProvider/src/OracleProvider/Metadata/Conventions/OracleConventionSetBuilder.cs +++ b/samples/OracleProvider/src/OracleProvider/Metadata/Conventions/OracleConventionSetBuilder.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Converters; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -54,7 +55,9 @@ public static ConventionSet Build() new RelationalConventionSetBuilderDependencies(oracleTypeMapper, currentContext: null, setFinder: null)) .AddConventions( new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(oracleTypeMapper)) + new CoreConventionSetBuilderDependencies( + oracleTypeMapper, + new ConstructorBindingFactory())) .CreateConventionSet()); } } diff --git a/samples/OracleProvider/test/OracleProvider.FunctionalTests/WithConstructorsOracleTest.cs b/samples/OracleProvider/test/OracleProvider.FunctionalTests/WithConstructorsOracleTest.cs new file mode 100644 index 00000000000..8da60a2fd16 --- /dev/null +++ b/samples/OracleProvider/test/OracleProvider.FunctionalTests/WithConstructorsOracleTest.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class WithConstructorsOracleTest : WithConstructorsTestBase + { + public WithConstructorsOracleTest(WithConstructorsOracleFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class WithConstructorsOracleFixture : WithConstructorsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => OracleTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.Entity>().ToTable("HasContext_DbContext"); + modelBuilder.Entity>().ToTable("HasContext_WithConstructorsContext"); + modelBuilder.Entity>().ToTable("HasContext_OtherContext"); + + modelBuilder.Entity( + b => { b.Property("_blogId").HasColumnName("BlogId"); }); + + modelBuilder.Entity( + b => { b.Property("_id").HasColumnName("Id"); }); + } + } + } +} diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 1035d688a0f..b9b6ae8f7a4 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -668,7 +668,8 @@ protected virtual void GenerateEntityTypeAnnotations( annotations, RelationshipDiscoveryConvention.NavigationCandidatesAnnotationName, RelationshipDiscoveryConvention.AmbiguousNavigationsAnnotationName, - InversePropertyAttributeConvention.InverseNavigationsAnnotationName); + InversePropertyAttributeConvention.InverseNavigationsAnnotationName, + CoreAnnotationNames.ConstructorBinding); if (annotations.Any()) { diff --git a/src/EFCore.InMemory/Query/Internal/IMaterializerFactory.cs b/src/EFCore.InMemory/Query/Internal/IMaterializerFactory.cs index 558f40474f9..837521f8d79 100644 --- a/src/EFCore.InMemory/Query/Internal/IMaterializerFactory.cs +++ b/src/EFCore.InMemory/Query/Internal/IMaterializerFactory.cs @@ -19,6 +19,6 @@ public interface IMaterializerFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Expression> CreateMaterializer([NotNull] IEntityType entityType); + Expression> CreateMaterializer([NotNull] IEntityType entityType); } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryModelVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryModelVisitor.cs index 620201b3a6b..87f416a0292 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryModelVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryModelVisitor.cs @@ -49,7 +49,7 @@ private static IEnumerable EntityQuery( QueryContext queryContext, IEntityType entityType, IKey key, - Func materializer, + Func materializer, bool queryStateManager) where TEntity : class => ((InMemoryQueryContext)queryContext).Store @@ -67,7 +67,8 @@ private static IEnumerable EntityQuery( key, new EntityLoadInfo( valueBuffer, - vr => materializer(t.EntityType, vr)), + queryContext.Context, + (vr, c) => materializer(t.EntityType, vr, c)), queryStateManager, throwOnNullKey: false); })); diff --git a/src/EFCore.InMemory/Query/Internal/MaterializerFactory.cs b/src/EFCore.InMemory/Query/Internal/MaterializerFactory.cs index c10e78aa8ba..06de627105a 100644 --- a/src/EFCore.InMemory/Query/Internal/MaterializerFactory.cs +++ b/src/EFCore.InMemory/Query/Internal/MaterializerFactory.cs @@ -35,7 +35,7 @@ public MaterializerFactory([NotNull] IEntityMaterializerSource entityMaterialize /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Expression> CreateMaterializer(IEntityType entityType) + public virtual Expression> CreateMaterializer(IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); @@ -45,17 +45,21 @@ var entityTypeParameter var valueBufferParameter = Expression.Parameter(typeof(ValueBuffer), "valueBuffer"); + var contextParameter + = Expression.Parameter(typeof(DbContext), "context"); + var concreteEntityTypes = entityType.GetConcreteTypesInHierarchy().ToList(); if (concreteEntityTypes.Count == 1) { - return Expression.Lambda>( + return Expression.Lambda>( _entityMaterializerSource .CreateMaterializeExpression( - concreteEntityTypes[0], valueBufferParameter), + concreteEntityTypes[0], valueBufferParameter, contextParameter), entityTypeParameter, - valueBufferParameter); + valueBufferParameter, + contextParameter); } var returnLabelTarget = Expression.Label(typeof(object)); @@ -71,7 +75,7 @@ var blockExpressions returnLabelTarget, _entityMaterializerSource .CreateMaterializeExpression( - concreteEntityTypes[0], valueBufferParameter))), + concreteEntityTypes[0], valueBufferParameter, contextParameter))), Expression.Label( returnLabelTarget, Expression.Default(returnLabelTarget.Type)) @@ -87,14 +91,15 @@ var blockExpressions Expression.Return( returnLabelTarget, _entityMaterializerSource - .CreateMaterializeExpression(concreteEntityType, valueBufferParameter)), + .CreateMaterializeExpression(concreteEntityType, valueBufferParameter, contextParameter)), blockExpressions[0]); } - return Expression.Lambda>( + return Expression.Lambda>( Expression.Block(blockExpressions), entityTypeParameter, - valueBufferParameter); + valueBufferParameter, + contextParameter); } } } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedEntityShaper`.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedEntityShaper`.cs index 419a9071580..c168f44f3a3 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedEntityShaper`.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedEntityShaper`.cs @@ -28,7 +28,7 @@ public BufferedEntityShaper( [NotNull] IQuerySource querySource, bool trackingQuery, [NotNull] IKey key, - [NotNull] Func materializer, + [NotNull] Func materializer, [CanBeNull] Dictionary typeIndexMap) : base(querySource, trackingQuery, key, materializer) { @@ -53,7 +53,7 @@ var entity = (TEntity)queryContext.QueryBuffer .GetEntity( Key, - new EntityLoadInfo(valueBuffer, Materializer, _typeIndexMap), + new EntityLoadInfo(valueBuffer, queryContext.Context, Materializer, _typeIndexMap), queryStateManager: IsTrackingQuery, throwOnNullKey: !AllowNullResult); diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedOffsetEntityShaper.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedOffsetEntityShaper.cs index 2aba39de9f4..a003f302c1f 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedOffsetEntityShaper.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/BufferedOffsetEntityShaper.cs @@ -25,7 +25,7 @@ public BufferedOffsetEntityShaper( [NotNull] IQuerySource querySource, bool trackingQuery, [NotNull] IKey key, - [NotNull] Func materializer, + [NotNull] Func materializer, [CanBeNull] Dictionary typeIndexMap) : base(querySource, trackingQuery, key, materializer, typeIndexMap) { diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EntityShaper.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EntityShaper.cs index 5107ba0487c..fc65f38fc30 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EntityShaper.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EntityShaper.cs @@ -23,7 +23,7 @@ protected EntityShaper( [NotNull] IQuerySource querySource, bool trackingQuery, [NotNull] IKey key, - [NotNull] Func materializer) + [NotNull] Func materializer) : base(querySource) { IsTrackingQuery = trackingQuery; @@ -47,7 +47,7 @@ protected EntityShaper( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected virtual Func Materializer { get; } + protected virtual Func Materializer { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IMaterializerFactory.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IMaterializerFactory.cs index 31ee91f401b..af088c058b5 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IMaterializerFactory.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IMaterializerFactory.cs @@ -22,7 +22,7 @@ public interface IMaterializerFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Expression> CreateMaterializer( + Expression> CreateMaterializer( [NotNull] IEntityType entityType, [NotNull] SelectExpression selectExpression, [NotNull] Func projectionAdder, diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs index ac9b6615df5..393da6c3ef1 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs @@ -41,7 +41,7 @@ public MaterializerFactory( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Expression> CreateMaterializer( + public virtual Expression> CreateMaterializer( IEntityType entityType, SelectExpression selectExpression, Func projectionAdder, @@ -57,6 +57,9 @@ public virtual Expression> CreateMaterializer( var valueBufferParameter = Expression.Parameter(typeof(ValueBuffer), "valueBuffer"); + var contextParameter + = Expression.Parameter(typeof(DbContext), "context"); + var concreteEntityTypes = entityType.GetConcreteTypesInHierarchy().ToList(); @@ -72,12 +75,13 @@ var concreteEntityTypes var materializer = _entityMaterializerSource .CreateMaterializeExpression( - concreteEntityTypes[0], valueBufferParameter, indexMap); + concreteEntityTypes[0], valueBufferParameter, contextParameter, indexMap); if (concreteEntityTypes.Count == 1 && concreteEntityTypes[0].RootType() == concreteEntityTypes[0]) { - return Expression.Lambda>(materializer, valueBufferParameter); + return Expression.Lambda>( + materializer, valueBufferParameter, contextParameter); } var discriminatorProperty = concreteEntityTypes[0].Relational().DiscriminatorProperty; @@ -98,7 +102,8 @@ var discriminatorPredicate selectExpression.Predicate = new DiscriminatorPredicateExpression(discriminatorPredicate, querySource); - return Expression.Lambda>(materializer, valueBufferParameter); + return Expression.Lambda>( + materializer, valueBufferParameter, contextParameter); } var discriminatorValueVariable @@ -160,7 +165,8 @@ var discriminatorValue materializer = _entityMaterializerSource - .CreateMaterializeExpression(concreteEntityType, valueBufferParameter, indexMap); + .CreateMaterializeExpression( + concreteEntityType, valueBufferParameter, contextParameter, indexMap); blockExpressions[1] = Expression.IfThenElse( @@ -177,9 +183,10 @@ var discriminatorValue selectExpression.Predicate = new DiscriminatorPredicateExpression(discriminatorPredicate, querySource); - return Expression.Lambda>( + return Expression.Lambda>( Expression.Block(new[] { discriminatorValueVariable }, blockExpressions), - valueBufferParameter); + valueBufferParameter, + contextParameter); } private static readonly MethodInfo _createUnableToDiscriminateException diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedEntityShaper.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedEntityShaper.cs index dfeffeef94e..29432ea7e72 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedEntityShaper.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedEntityShaper.cs @@ -24,7 +24,7 @@ public UnbufferedEntityShaper( [NotNull] IQuerySource querySource, bool trackingQuery, [NotNull] IKey key, - [NotNull] Func materializer) + [NotNull] Func materializer) : base(querySource, trackingQuery, key, materializer) { } @@ -51,7 +51,7 @@ public virtual TEntity Shape(QueryContext queryContext, ValueBuffer valueBuffer) } } - return (TEntity)Materializer(valueBuffer); + return (TEntity)Materializer(valueBuffer, queryContext.Context); } /// diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedOffsetEntityShaper.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedOffsetEntityShaper.cs index 3daa323080f..9ea24bd518a 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedOffsetEntityShaper.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/UnbufferedOffsetEntityShaper.cs @@ -24,7 +24,7 @@ public UnbufferedOffsetEntityShaper( [NotNull] IQuerySource querySource, bool trackingQuery, [NotNull] IKey key, - [NotNull] Func materializer) + [NotNull] Func materializer) : base(querySource, trackingQuery, key, materializer) { } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs index f918930a35b..aef25e2797b 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs @@ -340,7 +340,7 @@ private static IShaper CreateEntityShaper( IQuerySource querySource, bool trackingQuery, IKey key, - Func materializer, + Func materializer, Dictionary typeIndexMap, bool useQueryBuffer) where TEntity : class diff --git a/src/EFCore.Specification.Tests/DatabindingTestBase.cs b/src/EFCore.Specification.Tests/DatabindingTestBase.cs index 1534d745957..be2b3b3325b 100644 --- a/src/EFCore.Specification.Tests/DatabindingTestBase.cs +++ b/src/EFCore.Specification.Tests/DatabindingTestBase.cs @@ -862,7 +862,7 @@ public virtual void Entity_added_to_context_is_added_to_navigation_property_bind { using (var context = CreateF1Context()) { - var ferrari = context.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.Ferrari); + var ferrari = context.Teams.Single(t => t.Id == Team.Ferrari); var navBindingList = ((IListSource)ferrari.Drivers).GetList(); var larry = new Driver @@ -882,7 +882,7 @@ public virtual void Entity_added_to_navigation_property_binding_list_is_added_to { using (var context = CreateF1Context()) { - var ferrari = context.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.Ferrari); + var ferrari = context.Teams.Single(t => t.Id == Team.Ferrari); var navBindingList = ((IListSource)ferrari.Drivers).GetList(); var localDrivers = context.Drivers.Local; @@ -909,7 +909,7 @@ public virtual void Entity_removed_from_navigation_property_binding_list_is_remo { using (var context = CreateF1Context()) { - var ferrari = context.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.Ferrari); + var ferrari = context.Teams.Single(t => t.Id == Team.Ferrari); var navBindingList = ((IListSource)ferrari.Drivers).GetList(); var localDrivers = context.Drivers.Local; diff --git a/src/EFCore.Specification.Tests/FieldMappingTestBase.cs b/src/EFCore.Specification.Tests/FieldMappingTestBase.cs index ffce506e2d6..93f41266474 100644 --- a/src/EFCore.Specification.Tests/FieldMappingTestBase.cs +++ b/src/EFCore.Specification.Tests/FieldMappingTestBase.cs @@ -555,20 +555,20 @@ protected class BlogAuto : IBlogAccesor int IBlogAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IBlogAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { Posts = (IEnumerable)value; } + get => Posts; + set => Posts = (IEnumerable)value; } } @@ -584,26 +584,26 @@ protected class PostAuto : IPostAccesor int IPostAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IPostAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } int IPostAccesor.AccessBlogId { - get { return BlogId; } - set { BlogId = value; } + get => BlogId; + set => BlogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return Blog; } - set { Blog = (BlogAuto)value; } + get => Blog; + set => Blog = (BlogAuto)value; } } @@ -617,40 +617,40 @@ protected class BlogFull : IBlogAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - get { return _id; } - set { _id = value; } + get => _id; + set => _id = value; } // ReSharper disable once ConvertToAutoProperty public string Title { - get { return _title; } - set { _title = value; } + get => _title; + set => _title = value; } // ReSharper disable once ConvertToAutoProperty public IEnumerable Posts { - get { return _posts; } - set { _posts = (ICollection)value; } + get => _posts; + set => _posts = (ICollection)value; } int IBlogAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IBlogAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { Posts = (IEnumerable)value; } + get => Posts; + set => Posts = (IEnumerable)value; } } @@ -665,53 +665,53 @@ protected class PostFull : IPostAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - get { return _id; } - set { _id = value; } + get => _id; + set => _id = value; } // ReSharper disable once ConvertToAutoProperty public string Title { - get { return _title; } - set { _title = value; } + get => _title; + set => _title = value; } // ReSharper disable once ConvertToAutoProperty public int BlogId { - get { return _blogId; } - set { _blogId = value; } + get => _blogId; + set => _blogId = value; } // ReSharper disable once ConvertToAutoProperty public BlogFull Blog { - get { return _blog; } - set { _blog = value; } + get => _blog; + set => _blog = value; } int IPostAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IPostAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } int IPostAccesor.AccessBlogId { - get { return BlogId; } - set { BlogId = value; } + get => BlogId; + set => BlogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return Blog; } - set { Blog = (BlogFull)value; } + get => Blog; + set => Blog = (BlogFull)value; } } @@ -725,40 +725,40 @@ protected class BlogFullExplicit : IBlogAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - get { return _myid; } - set { _myid = value; } + get => _myid; + set => _myid = value; } // ReSharper disable once ConvertToAutoProperty public string Title { - get { return _mytitle; } - set { _mytitle = value; } + get => _mytitle; + set => _mytitle = value; } // ReSharper disable once ConvertToAutoProperty public IEnumerable Posts { - get { return _myposts; } - set { _myposts = (ICollection)value; } + get => _myposts; + set => _myposts = (ICollection)value; } int IBlogAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IBlogAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { Posts = (IEnumerable)value; } + get => Posts; + set => Posts = (IEnumerable)value; } } @@ -773,53 +773,53 @@ protected class PostFullExplicit : IPostAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - get { return _myid; } - set { _myid = value; } + get => _myid; + set => _myid = value; } // ReSharper disable once ConvertToAutoProperty public string Title { - get { return _mytitle; } - set { _mytitle = value; } + get => _mytitle; + set => _mytitle = value; } // ReSharper disable once ConvertToAutoProperty public int BlogId { - get { return _myblogId; } - set { _myblogId = value; } + get => _myblogId; + set => _myblogId = value; } // ReSharper disable once ConvertToAutoProperty public BlogFullExplicit Blog { - get { return _myblog; } - set { _myblog = value; } + get => _myblog; + set => _myblog = value; } int IPostAccesor.AccessId { - get { return Id; } - set { Id = value; } + get => Id; + set => Id = value; } string IPostAccesor.AccessTitle { - get { return Title; } - set { Title = value; } + get => Title; + set => Title = value; } int IPostAccesor.AccessBlogId { - get { return BlogId; } - set { BlogId = value; } + get => BlogId; + set => BlogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return Blog; } - set { Blog = (BlogFullExplicit)value; } + get => Blog; + set => Blog = (BlogFullExplicit)value; } } @@ -841,20 +841,20 @@ protected class BlogReadOnly : IBlogAccesor int IBlogAccesor.AccessId { - get { return Id; } - set { _id = value; } + get => Id; + set => _id = value; } string IBlogAccesor.AccessTitle { - get { return Title; } - set { _title = value; } + get => Title; + set => _title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { _posts = (ICollection)value; } + get => Posts; + set => _posts = (ICollection)value; } } @@ -880,26 +880,26 @@ protected class PostReadOnly : IPostAccesor int IPostAccesor.AccessId { - get { return Id; } - set { _id = value; } + get => Id; + set => _id = value; } string IPostAccesor.AccessTitle { - get { return Title; } - set { _title = value; } + get => Title; + set => _title = value; } int IPostAccesor.AccessBlogId { - get { return BlogId; } - set { _blogId = value; } + get => BlogId; + set => _blogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return Blog; } - set { _blog = (BlogReadOnly)value; } + get => Blog; + set => _blog = (BlogReadOnly)value; } } @@ -921,20 +921,20 @@ protected class BlogReadOnlyExplicit : IBlogAccesor int IBlogAccesor.AccessId { - get { return Id; } - set { _myid = value; } + get => Id; + set => _myid = value; } string IBlogAccesor.AccessTitle { - get { return Title; } - set { _mytitle = value; } + get => Title; + set => _mytitle = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { _myposts = (ICollection)value; } + get => Posts; + set => _myposts = (ICollection)value; } } @@ -960,26 +960,26 @@ protected class PostReadOnlyExplicit : IPostAccesor int IPostAccesor.AccessId { - get { return Id; } - set { _myid = value; } + get => Id; + set => _myid = value; } string IPostAccesor.AccessTitle { - get { return Title; } - set { _mytitle = value; } + get => Title; + set => _mytitle = value; } int IPostAccesor.AccessBlogId { - get { return BlogId; } - set { _myblogId = value; } + get => BlogId; + set => _myblogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return Blog; } - set { _myblog = (BlogReadOnlyExplicit)value; } + get => Blog; + set => _myblog = (BlogReadOnlyExplicit)value; } } @@ -992,35 +992,35 @@ protected class BlogWriteOnly : IBlogAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - set { _id = value; } + set => _id = value; } public string Title { - set { _title = value; } + set => _title = value; } public IEnumerable Posts { - set { _posts = (ICollection)value; } + set => _posts = (ICollection)value; } int IBlogAccesor.AccessId { - get { return _id; } - set { Id = value; } + get => _id; + set => Id = value; } string IBlogAccesor.AccessTitle { - get { return _title; } - set { Title = value; } + get => _title; + set => Title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return _posts; } - set { Posts = (IEnumerable)value; } + get => _posts; + set => Posts = (IEnumerable)value; } } @@ -1034,46 +1034,46 @@ protected class PostWriteOnly : IPostAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - set { _id = value; } + set => _id = value; } public string Title { - set { _title = value; } + set => _title = value; } public int BlogId { - set { _blogId = value; } + set => _blogId = value; } public BlogWriteOnly Blog { - set { _blog = value; } + set => _blog = value; } int IPostAccesor.AccessId { - get { return _id; } - set { Id = value; } + get => _id; + set => Id = value; } string IPostAccesor.AccessTitle { - get { return _title; } - set { Title = value; } + get => _title; + set => Title = value; } int IPostAccesor.AccessBlogId { - get { return _blogId; } - set { BlogId = value; } + get => _blogId; + set => BlogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return _blog; } - set { Blog = (BlogWriteOnly)value; } + get => _blog; + set => Blog = (BlogWriteOnly)value; } } @@ -1086,35 +1086,35 @@ protected class BlogWriteOnlyExplicit : IBlogAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - set { _myid = value; } + set => _myid = value; } public string Title { - set { _mytitle = value; } + set => _mytitle = value; } public IEnumerable Posts { - set { _myposts = (ICollection)value; } + set => _myposts = (ICollection)value; } int IBlogAccesor.AccessId { - get { return _myid; } - set { Id = value; } + get => _myid; + set => Id = value; } string IBlogAccesor.AccessTitle { - get { return _mytitle; } - set { Title = value; } + get => _mytitle; + set => Title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return _myposts; } - set { Posts = (IEnumerable)value; } + get => _myposts; + set => Posts = (IEnumerable)value; } } @@ -1128,46 +1128,46 @@ protected class PostWriteOnlyExplicit : IPostAccesor [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { - set { _myid = value; } + set => _myid = value; } public string Title { - set { _mytitle = value; } + set => _mytitle = value; } public int BlogId { - set { _myblogId = value; } + set => _myblogId = value; } public BlogWriteOnlyExplicit Blog { - set { _myblog = value; } + set => _myblog = value; } int IPostAccesor.AccessId { - get { return _myid; } - set { Id = value; } + get => _myid; + set => Id = value; } string IPostAccesor.AccessTitle { - get { return _mytitle; } - set { Title = value; } + get => _mytitle; + set => Title = value; } int IPostAccesor.AccessBlogId { - get { return _myblogId; } - set { BlogId = value; } + get => _myblogId; + set => BlogId = value; } IBlogAccesor IPostAccesor.AccessBlog { - get { return _myblog; } - set { Blog = (BlogWriteOnlyExplicit)value; } + get => _myblog; + set => Blog = (BlogWriteOnlyExplicit)value; } } @@ -1182,21 +1182,21 @@ protected class BlogFields : IBlogAccesor int IBlogAccesor.AccessId { - get { return _id; } - set { _id = value; } + get => _id; + set => _id = value; } // ReSharper disable once ConvertToAutoProperty string IBlogAccesor.AccessTitle { - get { return _title; } - set { _title = value; } + get => _title; + set => _title = value; } IEnumerable IBlogAccesor.AccessPosts { - get { return Posts; } - set { Posts = (IEnumerable)value; } + get => Posts; + set => Posts = (IEnumerable)value; } } diff --git a/src/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs b/src/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs index fcb2a0a53fa..8e2ca3af59f 100644 --- a/src/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs +++ b/src/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs @@ -939,128 +939,128 @@ private IEnumerable _requiredCompositeChildren public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public IEnumerable RequiredChildren { - get { return _requiredChildren; } - set { SetWithNotify(value, ref _requiredChildren); } + get => _requiredChildren; + set => SetWithNotify(value, ref _requiredChildren); } public IEnumerable OptionalChildren { - get { return _optionalChildren; } - set { SetWithNotify(value, ref _optionalChildren); } + get => _optionalChildren; + set => SetWithNotify(value, ref _optionalChildren); } public RequiredSingle1 RequiredSingle { - get { return _requiredSingle; } - set { SetWithNotify(value, ref _requiredSingle); } + get => _requiredSingle; + set => SetWithNotify(value, ref _requiredSingle); } public RequiredNonPkSingle1 RequiredNonPkSingle { - get { return _requiredNonPkSingle; } - set { SetWithNotify(value, ref _requiredNonPkSingle); } + get => _requiredNonPkSingle; + set => SetWithNotify(value, ref _requiredNonPkSingle); } public RequiredNonPkSingle1Derived RequiredNonPkSingleDerived { - get { return _requiredNonPkSingleDerived; } - set { SetWithNotify(value, ref _requiredNonPkSingleDerived); } + get => _requiredNonPkSingleDerived; + set => SetWithNotify(value, ref _requiredNonPkSingleDerived); } public RequiredNonPkSingle1MoreDerived RequiredNonPkSingleMoreDerived { - get { return _requiredNonPkSingleMoreDerived; } - set { SetWithNotify(value, ref _requiredNonPkSingleMoreDerived); } + get => _requiredNonPkSingleMoreDerived; + set => SetWithNotify(value, ref _requiredNonPkSingleMoreDerived); } public OptionalSingle1 OptionalSingle { - get { return _optionalSingle; } - set { SetWithNotify(value, ref _optionalSingle); } + get => _optionalSingle; + set => SetWithNotify(value, ref _optionalSingle); } public OptionalSingle1Derived OptionalSingleDerived { - get { return _optionalSingleDerived; } - set { SetWithNotify(value, ref _optionalSingleDerived); } + get => _optionalSingleDerived; + set => SetWithNotify(value, ref _optionalSingleDerived); } public OptionalSingle1MoreDerived OptionalSingleMoreDerived { - get { return _optionalSingleMoreDerived; } - set { SetWithNotify(value, ref _optionalSingleMoreDerived); } + get => _optionalSingleMoreDerived; + set => SetWithNotify(value, ref _optionalSingleMoreDerived); } public IEnumerable RequiredChildrenAk { - get { return _requiredChildrenAk; } - set { SetWithNotify(value, ref _requiredChildrenAk); } + get => _requiredChildrenAk; + set => SetWithNotify(value, ref _requiredChildrenAk); } public IEnumerable OptionalChildrenAk { - get { return _optionalChildrenAk; } - set { SetWithNotify(value, ref _optionalChildrenAk); } + get => _optionalChildrenAk; + set => SetWithNotify(value, ref _optionalChildrenAk); } public RequiredSingleAk1 RequiredSingleAk { - get { return _requiredSingleAk; } - set { SetWithNotify(value, ref _requiredSingleAk); } + get => _requiredSingleAk; + set => SetWithNotify(value, ref _requiredSingleAk); } public RequiredNonPkSingleAk1 RequiredNonPkSingleAk { - get { return _requiredNonPkSingleAk; } - set { SetWithNotify(value, ref _requiredNonPkSingleAk); } + get => _requiredNonPkSingleAk; + set => SetWithNotify(value, ref _requiredNonPkSingleAk); } public RequiredNonPkSingleAk1Derived RequiredNonPkSingleAkDerived { - get { return _requiredNonPkSingleAkDerived; } - set { SetWithNotify(value, ref _requiredNonPkSingleAkDerived); } + get => _requiredNonPkSingleAkDerived; + set => SetWithNotify(value, ref _requiredNonPkSingleAkDerived); } public RequiredNonPkSingleAk1MoreDerived RequiredNonPkSingleAkMoreDerived { - get { return _requiredNonPkSingleAkMoreDerived; } - set { SetWithNotify(value, ref _requiredNonPkSingleAkMoreDerived); } + get => _requiredNonPkSingleAkMoreDerived; + set => SetWithNotify(value, ref _requiredNonPkSingleAkMoreDerived); } public OptionalSingleAk1 OptionalSingleAk { - get { return _optionalSingleAk; } - set { SetWithNotify(value, ref _optionalSingleAk); } + get => _optionalSingleAk; + set => SetWithNotify(value, ref _optionalSingleAk); } public OptionalSingleAk1Derived OptionalSingleAkDerived { - get { return _optionalSingleAkDerived; } - set { SetWithNotify(value, ref _optionalSingleAkDerived); } + get => _optionalSingleAkDerived; + set => SetWithNotify(value, ref _optionalSingleAkDerived); } public OptionalSingleAk1MoreDerived OptionalSingleAkMoreDerived { - get { return _optionalSingleAkMoreDerived; } - set { SetWithNotify(value, ref _optionalSingleAkMoreDerived); } + get => _optionalSingleAkMoreDerived; + set => SetWithNotify(value, ref _optionalSingleAkMoreDerived); } public IEnumerable RequiredCompositeChildren { - get { return _requiredCompositeChildren; } - set { SetWithNotify(value, ref _requiredCompositeChildren); } + get => _requiredCompositeChildren; + set => SetWithNotify(value, ref _requiredCompositeChildren); } public override bool Equals(object obj) @@ -1081,26 +1081,26 @@ protected class Required1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Root Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public IEnumerable Children { - get { return _children; } - set { SetWithNotify(value, ref _children); } + get => _children; + set => SetWithNotify(value, ref _children); } public override bool Equals(object obj) @@ -1134,20 +1134,20 @@ protected class Required2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Required1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1183,32 +1183,32 @@ protected class Optional1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Root Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public IEnumerable Children { - get { return _children; } - set { SetWithNotify(value, ref _children); } + get => _children; + set => SetWithNotify(value, ref _children); } public ICollection CompositeChildren { - get { return _compositeChildren; } - set { SetWithNotify(value, ref _compositeChildren); } + get => _compositeChildren; + set => SetWithNotify(value, ref _compositeChildren); } public override bool Equals(object obj) @@ -1242,20 +1242,20 @@ protected class Optional2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Optional1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1289,20 +1289,20 @@ protected class RequiredSingle1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public RequiredSingle2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public override bool Equals(object obj) @@ -1321,14 +1321,14 @@ protected class RequiredSingle2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public RequiredSingle1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -1349,26 +1349,26 @@ protected class RequiredNonPkSingle1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int RootId { - get { return _rootId; } - set { SetWithNotify(value, ref _rootId); } + get => _rootId; + set => SetWithNotify(value, ref _rootId); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public RequiredNonPkSingle2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public override bool Equals(object obj) @@ -1387,14 +1387,14 @@ protected class RequiredNonPkSingle1Derived : RequiredNonPkSingle1 public int DerivedRootId { - get { return _derivedRootId; } - set { SetWithNotify(value, ref _derivedRootId); } + get => _derivedRootId; + set => SetWithNotify(value, ref _derivedRootId); } public Root DerivedRoot { - get { return _derivedRoot; } - set { SetWithNotify(value, ref _derivedRoot); } + get => _derivedRoot; + set => SetWithNotify(value, ref _derivedRoot); } public override bool Equals(object obj) => base.Equals(obj as RequiredNonPkSingle1Derived); @@ -1409,14 +1409,14 @@ protected class RequiredNonPkSingle1MoreDerived : RequiredNonPkSingle1Derived public int MoreDerivedRootId { - get { return _moreDerivedRootId; } - set { SetWithNotify(value, ref _moreDerivedRootId); } + get => _moreDerivedRootId; + set => SetWithNotify(value, ref _moreDerivedRootId); } public Root MoreDerivedRoot { - get { return _moreDerivedRoot; } - set { SetWithNotify(value, ref _moreDerivedRoot); } + get => _moreDerivedRoot; + set => SetWithNotify(value, ref _moreDerivedRoot); } public override bool Equals(object obj) => base.Equals(obj as RequiredNonPkSingle1MoreDerived); @@ -1432,20 +1432,20 @@ protected class RequiredNonPkSingle2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public RequiredNonPkSingle1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -1480,26 +1480,26 @@ protected class OptionalSingle1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int? RootId { - get { return _rootId; } - set { SetWithNotify(value, ref _rootId); } + get => _rootId; + set => SetWithNotify(value, ref _rootId); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public OptionalSingle2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public override bool Equals(object obj) @@ -1518,14 +1518,14 @@ protected class OptionalSingle1Derived : OptionalSingle1 public int? DerivedRootId { - get { return _derivedRootId; } - set { SetWithNotify(value, ref _derivedRootId); } + get => _derivedRootId; + set => SetWithNotify(value, ref _derivedRootId); } public Root DerivedRoot { - get { return _derivedRoot; } - set { SetWithNotify(value, ref _derivedRoot); } + get => _derivedRoot; + set => SetWithNotify(value, ref _derivedRoot); } public override bool Equals(object obj) => base.Equals(obj as OptionalSingle1Derived); @@ -1540,14 +1540,14 @@ protected class OptionalSingle1MoreDerived : OptionalSingle1Derived public int? MoreDerivedRootId { - get { return _moreDerivedRootId; } - set { SetWithNotify(value, ref _moreDerivedRootId); } + get => _moreDerivedRootId; + set => SetWithNotify(value, ref _moreDerivedRootId); } public Root MoreDerivedRoot { - get { return _moreDerivedRoot; } - set { SetWithNotify(value, ref _moreDerivedRoot); } + get => _moreDerivedRoot; + set => SetWithNotify(value, ref _moreDerivedRoot); } public override bool Equals(object obj) => base.Equals(obj as OptionalSingle1MoreDerived); @@ -1563,20 +1563,20 @@ protected class OptionalSingle2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int? BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public OptionalSingle1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -1613,38 +1613,38 @@ protected class RequiredAk1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Root Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public IEnumerable Children { - get { return _children; } - set { SetWithNotify(value, ref _children); } + get => _children; + set => SetWithNotify(value, ref _children); } public IEnumerable CompositeChildren { - get { return _compositeChildren; } - set { SetWithNotify(value, ref _compositeChildren); } + get => _compositeChildren; + set => SetWithNotify(value, ref _compositeChildren); } public override bool Equals(object obj) @@ -1679,26 +1679,26 @@ protected class RequiredAk2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public RequiredAk1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1719,20 +1719,20 @@ protected class RequiredComposite1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid ParentAlternateId { - get { return _parentAlternateId; } - set { SetWithNotify(value, ref _parentAlternateId); } + get => _parentAlternateId; + set => SetWithNotify(value, ref _parentAlternateId); } public Root Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1743,8 +1743,8 @@ public override bool Equals(object obj) public ICollection CompositeChildren { - get { return _compositeChildren; } - set { SetWithNotify(value, ref _compositeChildren); } + get => _compositeChildren; + set => SetWithNotify(value, ref _compositeChildren); } public override int GetHashCode() => _id; @@ -1760,32 +1760,32 @@ protected class OptionalOverlaping2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid ParentAlternateId { - get { return _parentAlternateId; } - set { SetWithNotify(value, ref _parentAlternateId); } + get => _parentAlternateId; + set => SetWithNotify(value, ref _parentAlternateId); } public int? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public RequiredComposite1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public override bool Equals(object obj) @@ -1806,26 +1806,26 @@ protected class RequiredComposite2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid ParentAlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public int ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public RequiredAk1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1862,38 +1862,38 @@ protected class OptionalAk1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public Root Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public IEnumerable Children { - get { return _children; } - set { SetWithNotify(value, ref _children); } + get => _children; + set => SetWithNotify(value, ref _children); } public ICollection CompositeChildren { - get { return _compositeChildren; } - set { SetWithNotify(value, ref _compositeChildren); } + get => _compositeChildren; + set => SetWithNotify(value, ref _compositeChildren); } public override bool Equals(object obj) @@ -1928,26 +1928,26 @@ protected class OptionalAk2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public OptionalAk1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public override bool Equals(object obj) @@ -1970,38 +1970,38 @@ protected class OptionalComposite2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid ParentAlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public int? ParentId { - get { return _parentId; } - set { SetWithNotify(value, ref _parentId); } + get => _parentId; + set => SetWithNotify(value, ref _parentId); } public OptionalAk1 Parent { - get { return _parent; } - set { SetWithNotify(value, ref _parent); } + get => _parent; + set => SetWithNotify(value, ref _parent); } public int? Parent2Id { - get { return _parent2Id; } - set { SetWithNotify(value, ref _parent2Id); } + get => _parent2Id; + set => SetWithNotify(value, ref _parent2Id); } public Optional1 Parent2 { - get { return _parent2; } - set { SetWithNotify(value, ref _parent2); } + get => _parent2; + set => SetWithNotify(value, ref _parent2); } public override bool Equals(object obj) @@ -2038,38 +2038,38 @@ protected class RequiredSingleAk1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid RootId { - get { return _rootId; } - set { SetWithNotify(value, ref _rootId); } + get => _rootId; + set => SetWithNotify(value, ref _rootId); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public RequiredSingleAk2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public RequiredSingleComposite2 SingleComposite { - get { return _singleComposite; } - set { SetWithNotify(value, ref _singleComposite); } + get => _singleComposite; + set => SetWithNotify(value, ref _singleComposite); } public override bool Equals(object obj) @@ -2090,26 +2090,26 @@ protected class RequiredSingleAk2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public RequiredSingleAk1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -2130,26 +2130,26 @@ protected class RequiredSingleComposite2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid BackAlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public int BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public RequiredSingleAk1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -2171,32 +2171,32 @@ protected class RequiredNonPkSingleAk1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid RootId { - get { return _rootId; } - set { SetWithNotify(value, ref _rootId); } + get => _rootId; + set => SetWithNotify(value, ref _rootId); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public RequiredNonPkSingleAk2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public override bool Equals(object obj) @@ -2215,14 +2215,14 @@ protected class RequiredNonPkSingleAk1Derived : RequiredNonPkSingleAk1 public Guid DerivedRootId { - get { return _derivedRootId; } - set { SetWithNotify(value, ref _derivedRootId); } + get => _derivedRootId; + set => SetWithNotify(value, ref _derivedRootId); } public Root DerivedRoot { - get { return _derivedRoot; } - set { SetWithNotify(value, ref _derivedRoot); } + get => _derivedRoot; + set => SetWithNotify(value, ref _derivedRoot); } public override bool Equals(object obj) => base.Equals(obj as RequiredNonPkSingleAk1Derived); @@ -2237,14 +2237,14 @@ protected class RequiredNonPkSingleAk1MoreDerived : RequiredNonPkSingleAk1Derive public Guid MoreDerivedRootId { - get { return _moreDerivedRootId; } - set { SetWithNotify(value, ref _moreDerivedRootId); } + get => _moreDerivedRootId; + set => SetWithNotify(value, ref _moreDerivedRootId); } public Root MoreDerivedRoot { - get { return _moreDerivedRoot; } - set { SetWithNotify(value, ref _moreDerivedRoot); } + get => _moreDerivedRoot; + set => SetWithNotify(value, ref _moreDerivedRoot); } public override bool Equals(object obj) => base.Equals(obj as RequiredNonPkSingleAk1MoreDerived); @@ -2261,26 +2261,26 @@ protected class RequiredNonPkSingleAk2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public RequiredNonPkSingleAk1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -2317,38 +2317,38 @@ protected class OptionalSingleAk1 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid? RootId { - get { return _rootId; } - set { SetWithNotify(value, ref _rootId); } + get => _rootId; + set => SetWithNotify(value, ref _rootId); } public Root Root { - get { return _root; } - set { SetWithNotify(value, ref _root); } + get => _root; + set => SetWithNotify(value, ref _root); } public OptionalSingleComposite2 SingleComposite { - get { return _singleComposite; } - set { SetWithNotify(value, ref _singleComposite); } + get => _singleComposite; + set => SetWithNotify(value, ref _singleComposite); } public OptionalSingleAk2 Single { - get { return _single; } - set { SetWithNotify(value, ref _single); } + get => _single; + set => SetWithNotify(value, ref _single); } public override bool Equals(object obj) @@ -2367,14 +2367,14 @@ protected class OptionalSingleAk1Derived : OptionalSingleAk1 public Guid? DerivedRootId { - get { return _derivedRootId; } - set { SetWithNotify(value, ref _derivedRootId); } + get => _derivedRootId; + set => SetWithNotify(value, ref _derivedRootId); } public Root DerivedRoot { - get { return _derivedRoot; } - set { SetWithNotify(value, ref _derivedRoot); } + get => _derivedRoot; + set => SetWithNotify(value, ref _derivedRoot); } public override bool Equals(object obj) => base.Equals(obj as OptionalSingleAk1Derived); @@ -2389,14 +2389,14 @@ protected class OptionalSingleAk1MoreDerived : OptionalSingleAk1Derived public Guid? MoreDerivedRootId { - get { return _moreDerivedRootId; } - set { SetWithNotify(value, ref _moreDerivedRootId); } + get => _moreDerivedRootId; + set => SetWithNotify(value, ref _moreDerivedRootId); } public Root MoreDerivedRoot { - get { return _moreDerivedRoot; } - set { SetWithNotify(value, ref _moreDerivedRoot); } + get => _moreDerivedRoot; + set => SetWithNotify(value, ref _moreDerivedRoot); } public override bool Equals(object obj) => base.Equals(obj as OptionalSingleAk1MoreDerived); @@ -2413,26 +2413,26 @@ protected class OptionalSingleAk2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid AlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public Guid? BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public OptionalSingleAk1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -2453,26 +2453,26 @@ protected class OptionalSingleComposite2 : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public Guid ParentAlternateId { - get { return _alternateId; } - set { SetWithNotify(value, ref _alternateId); } + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); } public int? BackId { - get { return _backId; } - set { SetWithNotify(value, ref _backId); } + get => _backId; + set => SetWithNotify(value, ref _backId); } public OptionalSingleAk1 Back { - get { return _back; } - set { SetWithNotify(value, ref _back); } + get => _back; + set => SetWithNotify(value, ref _back); } public override bool Equals(object obj) @@ -2506,20 +2506,20 @@ protected class BadCustomer : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int Status { - get { return _status; } - set { SetWithNotify(value, ref _status); } + get => _status; + set => SetWithNotify(value, ref _status); } public ICollection BadOrders { - get { return _badOrders; } - set { SetWithNotify(value, ref _badOrders); } + get => _badOrders; + set => SetWithNotify(value, ref _badOrders); } } @@ -2531,20 +2531,20 @@ protected class BadOrder : NotifyingEntity public int Id { - get { return _id; } - set { SetWithNotify(value, ref _id); } + get => _id; + set => SetWithNotify(value, ref _id); } public int? BadCustomerId { - get { return _badCustomerId; } - set { SetWithNotify(value, ref _badCustomerId); } + get => _badCustomerId; + set => SetWithNotify(value, ref _badCustomerId); } public BadCustomer BadCustomer { - get { return _badCustomer; } - set { SetWithNotify(value, ref _badCustomer); } + get => _badCustomer; + set => SetWithNotify(value, ref _badCustomer); } } diff --git a/src/EFCore.Specification.Tests/LoadTestBase.cs b/src/EFCore.Specification.Tests/LoadTestBase.cs index 49eccf89552..e4818202c78 100644 --- a/src/EFCore.Specification.Tests/LoadTestBase.cs +++ b/src/EFCore.Specification.Tests/LoadTestBase.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; // ReSharper disable InconsistentNaming @@ -19,6 +21,1249 @@ public abstract class LoadTestBase : IClassFixture protected TFixture Fixture { get; } + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_collection(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var collectionEntry = context.Entry(parent).Collection(e => e.Children); + + context.Entry(parent).State = state; + + Assert.False(collectionEntry.IsLoaded); + + Assert.NotNull(parent.Children); + + Assert.True(collectionEntry.IsLoaded); + + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, parent.Children.Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Single(e => e.Id == 12); + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, child.Parent); + Assert.Same(child, parent.Children.Single()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.Single); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.Single); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(parent.Single); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.Single); + Assert.Same(parent, single.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.SinglePkToPk); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(parent.SinglePkToPk); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.SinglePkToPk); + Assert.Same(parent, single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Attach( + new Child(context.GetService().Load) + { + Id = 767, ParentId = null + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + Assert.Null(child.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Attach( + new Single(context.GetService().Load) + { + Id = 767, ParentId = null + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_collection_not_found(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Attach( + new Parent(context.GetService().Load) + { + Id = 767, AlternateId = "NewRoot" + }).Entity; + + ClearLog(); + + var collectionEntry = context.Entry(parent).Collection(e => e.Children); + + context.Entry(parent).State = state; + + Assert.False(collectionEntry.IsLoaded); + + Assert.Empty(parent.Children); + + Assert.True(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(0, parent.Children.Count()); + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_not_found(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Attach( + new Child(context.GetService().Load) + { + Id = 767, ParentId = 787 + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + Assert.Null(child.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_not_found(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Attach( + new Single(context.GetService().Load) + { + Id = 767, ParentId = 787 + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(single.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Attach( + new Parent(context.GetService().Load) + { + Id = 767, AlternateId = "NewRoot" + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.Single); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(parent.Single); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(parent.Single); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_collection_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Include(e => e.Children).Single(); + + ClearLog(); + + var collectionEntry = context.Entry(parent).Collection(e => e.Children); + + context.Entry(parent).State = state; + + Assert.True(collectionEntry.IsLoaded); + + Assert.NotNull(parent.Children); + + Assert.True(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, parent.Children.Count()); + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Include(e => e.Parent).Single(e => e.Id == 12); + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.True(referenceEntry.IsLoaded); + + Assert.NotNull(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, child.Parent); + Assert.Same(child, parent.Children.Single()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Include(e => e.Parent).Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.True(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.Single); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Include(e => e.Single).Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.Single); + + context.Entry(parent).State = state; + + Assert.True(referenceEntry.IsLoaded); + + Assert.NotNull(parent.Single); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.Single); + Assert.Same(parent, single.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Include(e => e.Parent).Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.True(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.SinglePkToPk); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already_loaded(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Include(e => e.SinglePkToPk).Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk); + + context.Entry(parent).State = state; + + Assert.True(referenceEntry.IsLoaded); + + Assert.NotNull(parent.SinglePkToPk); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.SinglePkToPk); + Assert.Same(parent, single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_alternate_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Single(e => e.Id == 32); + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, child.Parent); + Assert.Same(child, parent.ChildrenAk.Single()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_alternate_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.SingleAk); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_alternate_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.SingleAk); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(parent.SingleAk); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.SingleAk); + Assert.Same(parent, single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_alternate_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Attach( + new ChildAk(context.GetService().Load) + { + Id = 767, ParentId = null + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + Assert.Null(child.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_alternate_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Attach( + new SingleAk(context.GetService().Load) + { + Id = 767, ParentId = null + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_collection_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenShadowFk); + + context.Entry(parent).State = state; + + Assert.False(collectionEntry.IsLoaded); + + Assert.NotNull(parent.ChildrenShadowFk); + + Assert.True(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, parent.ChildrenShadowFk.Count()); + Assert.All(parent.ChildrenShadowFk.Select(e => e.Parent), c => Assert.Same(parent, c)); + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Single(e => e.Id == 52); + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, child.Parent); + Assert.Same(child, parent.ChildrenShadowFk.Single()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.SingleShadowFk); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.SingleShadowFk); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(parent.SingleShadowFk); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.SingleShadowFk); + Assert.Same(parent, single.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Attach( + new ChildShadowFk(context.GetService().Load) + { + Id = 767 + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + Assert.Null(child.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Attach( + new SingleShadowFk(context.GetService().Load) + { + Id = 767 + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(single.Parent); + } + } + + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_collection_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenCompositeKey); + + context.Entry(parent).State = state; + + Assert.False(collectionEntry.IsLoaded); + + Assert.NotNull(parent.ChildrenCompositeKey); + + Assert.True(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, parent.ChildrenCompositeKey.Count()); + Assert.All(parent.ChildrenCompositeKey.Select(e => e.Parent), c => Assert.Same(parent, c)); + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Single(e => e.Id == 52); + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, child.Parent); + Assert.Same(child, parent.ChildrenCompositeKey.Single()); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var parent = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(parent, single.Parent); + Assert.Same(single, parent.SingleCompositeKey); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + ClearLog(); + + var referenceEntry = context.Entry(parent).Reference(e => e.SingleCompositeKey); + + context.Entry(parent).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.NotNull(parent.SingleCompositeKey); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + + var single = context.ChangeTracker.Entries().Single().Entity; + + Assert.Same(single, parent.SingleCompositeKey); + Assert.Same(parent, single.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Attach( + new ChildCompositeKey(context.GetService().Load) + { + Id = 767, ParentId = 567 + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(child).Reference(e => e.Parent); + + context.Entry(child).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(child.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + Assert.Null(child.Parent); + } + } + + [Theory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Deleted)] + public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_composite_key(EntityState state) + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var single = context.Attach( + new SingleCompositeKey(context.GetService().Load) + { + Id = 767, ParentAlternateId = "Boot" + }).Entity; + + ClearLog(); + + var referenceEntry = context.Entry(single).Reference(e => e.Parent); + + context.Entry(single).State = state; + + Assert.False(referenceEntry.IsLoaded); + + Assert.Null(single.Parent); + + Assert.True(referenceEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Null(single.Parent); + } + } + + [Fact] + public virtual void Lazy_load_collection_for_detached_throws() + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + context.Entry(parent).State = EntityState.Detached; + + Assert.Equal( + CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)), + Assert.Throws( + () => parent.Children).Message); + } + } + + [Fact] + public virtual void Lazy_load_reference_to_principal_for_detached_throws() + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var child = context.Set().Single(e => e.Id == 12); + + context.Entry(child).State = EntityState.Detached; + + Assert.Equal( + CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)), + Assert.Throws( + () => child.Parent).Message); + } + } + + [Fact] + public virtual void Lazy_load_reference_to_dependent_for_detached_throws() + { + using (var context = CreateContext(lazyLoadingEnabled: true)) + { + var parent = context.Set().Single(); + + context.Entry(parent).State = EntityState.Detached; + + Assert.Equal( + CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)), + Assert.Throws( + () => parent.Single).Message); + } + } + [Theory] [InlineData(EntityState.Unchanged, true)] [InlineData(EntityState.Unchanged, false)] @@ -52,6 +1297,7 @@ public virtual async Task Load_collection(EntityState state, bool async) Assert.True(collectionEntry.IsLoaded); RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; Assert.Equal(2, parent.Children.Count()); Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); @@ -4278,106 +5524,323 @@ public virtual void Query_reference_to_dependent_using_string_for_detached_throw protected class Parent { + private readonly Action _loader; + private IEnumerable _children; + private SinglePkToPk _singlePkToPk; + private Single _single; + private IEnumerable _childrenAk; + private SingleAk _singleAk; + private IEnumerable _childrenShadowFk; + private SingleShadowFk _singleShadowFk; + private IEnumerable _childrenCompositeKey; + private SingleCompositeKey _singleCompositeKey; + + public Parent() + { + } + + public Parent(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public string AlternateId { get; set; } - public IEnumerable Children { get; set; } - public SinglePkToPk SinglePkToPk { get; set; } - public Single Single { get; set; } + public IEnumerable Children + { + get => _loader.Load(this, ref _children); + set => _children = value; + } + + public SinglePkToPk SinglePkToPk + { + get => _loader.Load(this, ref _singlePkToPk); + set => _singlePkToPk = value; + } + + public Single Single + { + get => _loader.Load(this, ref _single); + set => _single = value; + } + + public IEnumerable ChildrenAk + { + get => _loader.Load(this, ref _childrenAk); + set => _childrenAk = value; + } + + public SingleAk SingleAk + { + get => _loader.Load(this, ref _singleAk); + set => _singleAk = value; + } + + public IEnumerable ChildrenShadowFk + { + get => _loader.Load(this, ref _childrenShadowFk); + set => _childrenShadowFk = value; + } - public IEnumerable ChildrenAk { get; set; } - public SingleAk SingleAk { get; set; } + public SingleShadowFk SingleShadowFk + { + get => _loader.Load(this, ref _singleShadowFk); + set => _singleShadowFk = value; + } - public IEnumerable ChildrenShadowFk { get; set; } - public SingleShadowFk SingleShadowFk { get; set; } + public IEnumerable ChildrenCompositeKey + { + get => _loader.Load(this, ref _childrenCompositeKey); + set => _childrenCompositeKey = value; + } - public IEnumerable ChildrenCompositeKey { get; set; } - public SingleCompositeKey SingleCompositeKey { get; set; } + public SingleCompositeKey SingleCompositeKey + { + get => _loader.Load(this, ref _singleCompositeKey); + set => _singleCompositeKey = value; + } } protected class Child { + private readonly Action _loader; + private Parent _parent; + + public Child() + { + } + + public Child(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public int? ParentId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class SinglePkToPk { + private readonly Action _loader; + private Parent _parent; + + public SinglePkToPk() + { + } + + protected SinglePkToPk(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } - public Parent Parent { get; set; } + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class Single { + private readonly Action _loader; + private Parent _parent; + + public Single() + { + } + + public Single(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public int? ParentId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class ChildAk { + private readonly Action _loader; + private Parent _parent; + + public ChildAk() + { + } + + public ChildAk(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public string ParentId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class SingleAk { + private readonly Action _loader; + private Parent _parent; + + public SingleAk() + { + } + + public SingleAk(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public string ParentId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class ChildShadowFk { + private readonly Action _loader; + private Parent _parent; + + public ChildShadowFk() + { + } + + public ChildShadowFk(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } - public Parent Parent { get; set; } + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class SingleShadowFk { + private readonly Action _loader; + private Parent _parent; + + public SingleShadowFk() + { + } + + public SingleShadowFk(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } - public Parent Parent { get; set; } + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class ChildCompositeKey { + private readonly Action _loader; + private Parent _parent; + + public ChildCompositeKey() + { + } + + public ChildCompositeKey(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public int? ParentId { get; set; } public string ParentAlternateId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } protected class SingleCompositeKey { + private readonly Action _loader; + private Parent _parent; + + public SingleCompositeKey() + { + } + + public SingleCompositeKey(Action lazyLoader) + { + _loader = lazyLoader; + } + [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public int? ParentId { get; set; } public string ParentAlternateId { get; set; } - public Parent Parent { get; set; } + + public Parent Parent + { + get => _loader.Load(this, ref _parent); + set => _parent = value; + } } - protected DbContext CreateContext() => Fixture.CreateContext(); + protected DbContext CreateContext(bool lazyLoadingEnabled = false) + { + var context = Fixture.CreateContext(); + context.ChangeTracker.LazyLoadingEnabled = lazyLoadingEnabled; + + return context; + } protected virtual void ClearLog() { diff --git a/src/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs b/src/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs index ff717e42e31..cc3b1048141 100644 --- a/src/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs +++ b/src/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs @@ -123,13 +123,13 @@ public virtual Task Two_concurrency_issues_in_one_to_one_related_entities_can_be return ConcurrencyTestAsync( c => { - var team = c.Teams.Include(t => t.Chassis).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); team.Chassis.Name = "MP4-25b"; team.Principal = "Larry David"; }, c => { - var team = c.Teams.Include(t => t.Chassis).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); team.Chassis.Name = "MP4-25c"; team.Principal = "Jerry Seinfeld"; }, @@ -155,7 +155,7 @@ public virtual Task Two_concurrency_issues_in_one_to_one_related_entities_can_be }, c => { - var team = c.Teams.Include(t => t.Chassis).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); Assert.Equal("MP4-25b", team.Chassis.Name); Assert.Equal("Larry David", team.Principal); }); @@ -167,13 +167,13 @@ public virtual Task Two_concurrency_issues_in_one_to_many_related_entities_can_b return ConcurrencyTestAsync( c => { - var team = c.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); team.Drivers.Single(d => d.Name == "Jenson Button").Poles = 1; team.Principal = "Larry David"; }, c => { - var team = c.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); team.Drivers.Single(d => d.Name == "Jenson Button").Poles = 2; team.Principal = "Jerry Seinfeld"; }, @@ -199,7 +199,7 @@ public virtual Task Two_concurrency_issues_in_one_to_many_related_entities_can_b }, c => { - var team = c.Teams.Include(t => t.Drivers).Single(t => t.Id == Team.McLaren); + var team = c.Teams.Single(t => t.Id == Team.McLaren); Assert.Equal(1, team.Drivers.Single(d => d.Name == "Jenson Button").Poles); Assert.Equal("Larry David", team.Principal); }); @@ -224,7 +224,7 @@ public virtual Task Concurrency_issue_where_the_FK_is_the_concurrency_token_can_ c => Assert.Equal( "Cosworth", - c.Engines.Include(e => e.EngineSupplier).Single(e => e.Name == "056").EngineSupplier.Name)); + c.Engines.Single(e => e.Name == "056").EngineSupplier.Name)); } #endregion diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Chassis.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Chassis.cs index 6dba6757712..c76c2af8816 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Chassis.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Chassis.cs @@ -5,10 +5,30 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Chassis { - public int TeamId { get; set; } + private readonly ILazyLoader _loader; + private Team _team; - public string Name { get; set; } + public Chassis() + { + } + + private Chassis( + ILazyLoader loader, + int teamId, + string name) + { + _loader = loader; + TeamId = teamId; + Name = name; + } - public virtual Team Team { get; set; } + public int TeamId { get; set; } + public string Name { get; set; } + + public virtual Team Team + { + get => _loader.Load(this, ref _team); + set => _team = value; + } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Driver.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Driver.cs index b7a3eb7a2cf..7ba7c6be26d 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Driver.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Driver.cs @@ -5,8 +5,41 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Driver { - public int Id { get; set; } + private readonly ILazyLoader _loader; + private Team _team; + + public Driver() + { + } + + protected Driver( + ILazyLoader loader, + int id, + string name, + int? carNumber, + int championships, + int races, + int wins, + int podiums, + int poles, + int fastestLaps, + int teamId) + { + _loader = loader; + Id = id; + Name = name; + CarNumber = carNumber; + Championships = championships; + Races = races; + Wins = wins; + Podiums = podiums; + Poles = poles; + FastestLaps = fastestLaps; + TeamId = teamId; + } + + public int Id { get; set; } public string Name { get; set; } public int? CarNumber { get; set; } public int Championships { get; set; } @@ -16,7 +49,12 @@ public class Driver public int Poles { get; set; } public int FastestLaps { get; set; } - public virtual Team Team { get; set; } + public virtual Team Team + { + get => _loader.Load(this, ref _team); + set => _team = value; + } + public int TeamId { get; set; } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs index 33865c9b46c..90fb73f85cd 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs @@ -7,18 +7,44 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Engine { - public int Id { get; set; } + private readonly ILazyLoader _loader; + private EngineSupplier _engineSupplier; + private ICollection _teams; + private ICollection _gearboxes; + + public Engine() + { + } + + public Engine(ILazyLoader loader, int id, string name) + { + _loader = loader; + Id = id; + Name = name; + } + public int Id { get; set; } public string Name { get; set; } public Location StorageLocation { get; set; } public int EngineSupplierId { get; set; } - - public virtual EngineSupplier EngineSupplier { get; set; } - - public virtual ICollection Teams { get; set; } - - public virtual ICollection Gearboxes { get; set; } // Uni-directional + public virtual EngineSupplier EngineSupplier + { + get => _loader.Load(this, ref _engineSupplier); + set => _engineSupplier = value; + } + + public virtual ICollection Teams + { + get => _loader.Load(this, ref _teams); + set => _teams = value; + } + + public virtual ICollection Gearboxes + { + get => _loader.Load(this, ref _gearboxes); + set => _gearboxes = value; + } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs index 7187fb5bf2f..76f6356e45d 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs @@ -7,10 +7,27 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class EngineSupplier { - public int Id { get; set; } + private readonly ILazyLoader _loader; + private ICollection _engines; + + public EngineSupplier() + { + } + private EngineSupplier(ILazyLoader loader, int id, string name) + { + _loader = loader; + Id = id; + Name = name; + } + + public int Id { get; set; } public string Name { get; set; } - public virtual ICollection Engines { get; set; } + public virtual ICollection Engines + { + get => _loader.Load(this, ref _engines); + set => _engines = value; + } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Gearbox.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Gearbox.cs index 8f5940d8f0d..0ddd3914f52 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Gearbox.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Gearbox.cs @@ -5,6 +5,16 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Gearbox { + public Gearbox() + { + } + + private Gearbox(int id, string name) + { + Id = id; + Name = name; + } + public int Id { get; set; } public string Name { get; set; } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Location.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Location.cs index 3f28a5472f9..f2abf7e7a87 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Location.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Location.cs @@ -5,8 +5,17 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Location { - public double Latitude { get; set; } + public Location() + { + } + + private Location(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + public double Latitude { get; set; } public double Longitude { get; set; } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/SponsorDetails.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/SponsorDetails.cs index d9213f39080..088490e4da3 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/SponsorDetails.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/SponsorDetails.cs @@ -5,6 +5,16 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class SponsorDetails { + public SponsorDetails() + { + } + + private SponsorDetails(int days, decimal space) + { + Days = days; + Space = space; + } + public int Days { get; set; } public decimal Space { get; set; } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Team.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Team.cs index 2f62ca5c366..d77b69fa9d7 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Team.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Team.cs @@ -10,11 +10,48 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class Team { + private readonly ILazyLoader _loader; private readonly ObservableCollection _drivers = new ObservableCollectionListSource(); private readonly ObservableCollection _sponsors = new ObservableCollection(); + private Engine _engine; + private Chassis _chassis; + private Gearbox _gearbox; - public int Id { get; set; } + public Team() + { + } + + private Team( + ILazyLoader loader, + int id, + string name, + string constructor, + string tire, + string principal, + int constructorsChampionships, + int driversChampionships, + int races, + int victories, + int poles, + int fastestLaps, + int? gearboxId) + { + _loader = loader; + Id = id; + Name = name; + Constructor = constructor; + Tire = tire; + Principal = principal; + ConstructorsChampionships = constructorsChampionships; + DriversChampionships = driversChampionships; + Races = races; + Victories = victories; + Poles = poles; + FastestLaps = fastestLaps; + GearboxId = gearboxId; + } + public int Id { get; set; } public string Name { get; set; } public string Constructor { get; set; } public string Tire { get; set; } @@ -26,17 +63,37 @@ public class Team public int Poles { get; set; } public int FastestLaps { get; set; } - public virtual Engine Engine { get; set; } // Independent Association + public virtual Engine Engine + { + get => _loader.Load(this, ref _engine); + set => _engine = value; + } - public virtual Chassis Chassis { get; set; } + public virtual Chassis Chassis + { + get => _loader.Load(this, ref _chassis); + set => _chassis = value; + } - public virtual ICollection Drivers => _drivers; + public virtual ICollection Drivers + { + get + { + _loader?.Load(this); + return _drivers; + } + } [NotMapped] public virtual ICollection Sponsors => _sponsors; public int? GearboxId { get; set; } - public virtual Gearbox Gearbox { get; set; } // Uni-directional + + public virtual Gearbox Gearbox + { + get => _loader.Load(this, ref _gearbox); + set => _gearbox = value; + } public const int McLaren = 1; public const int Mercedes = 2; diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TestDriver.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TestDriver.cs index b7a1516995f..d665e059141 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TestDriver.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TestDriver.cs @@ -5,5 +5,24 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class TestDriver : Driver { + public TestDriver() + { + } + + private TestDriver( + ILazyLoader loader, + int id, + string name, + int? carNumber, + int championships, + int races, + int wins, + int podiums, + int poles, + int fastestLaps, + int teamId) + : base(loader, id, name, carNumber, championships, races, wins, podiums, poles, fastestLaps, teamId) + { + } } } diff --git a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TitleSponsor.cs b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TitleSponsor.cs index 54449b1bd39..fc8dc03448a 100644 --- a/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TitleSponsor.cs +++ b/src/EFCore.Specification.Tests/TestModels/ConcurrencyModel/TitleSponsor.cs @@ -5,6 +5,22 @@ namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel { public class TitleSponsor : Sponsor { - public SponsorDetails Details { get; set; } + private readonly ILazyLoader _loader; + private SponsorDetails _details; + + public TitleSponsor() + { + } + + private TitleSponsor(ILazyLoader loader) + { + _loader = loader; + } + + public SponsorDetails Details + { + get => _loader.Load(this, ref _details); + set => _details = value; + } } } diff --git a/src/EFCore.Specification.Tests/TestUtilities/TestPocoLoadingExtensions.cs b/src/EFCore.Specification.Tests/TestUtilities/TestPocoLoadingExtensions.cs new file mode 100644 index 00000000000..01f033b9dc6 --- /dev/null +++ b/src/EFCore.Specification.Tests/TestUtilities/TestPocoLoadingExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + public static class TestPocoLoadingExtensions + { + public static TRelated Load( + this Action loader, + object entity, + ref TRelated navigationField, + [CallerMemberName] string navigationName = null) + where TRelated : class + { + loader?.Invoke(entity, navigationName); + + return navigationField; + } + } +} diff --git a/src/EFCore.Specification.Tests/WithConstructorsTestBase.cs b/src/EFCore.Specification.Tests/WithConstructorsTestBase.cs new file mode 100644 index 00000000000..7291111d3d1 --- /dev/null +++ b/src/EFCore.Specification.Tests/WithConstructorsTestBase.cs @@ -0,0 +1,400 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class WithConstructorsTestBase : IClassFixture + where TFixture : WithConstructorsTestBase.WithConstructorsFixtureBase, new() + { + protected WithConstructorsTestBase(TFixture fixture) => Fixture = fixture; + + protected TFixture Fixture { get; } + + protected DbContext CreateContext() => Fixture.CreateContext(); + + protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + [Fact] + public virtual void Query_and_update_using_constructors_with_property_parameters() + { + TestHelpers.ExecuteWithStrategyInTransaction( + CreateContext, UseTransaction, + context => + { + var blog = context.Set().Include(e => e.Posts).Single(); + + Assert.Equal("Puppies", blog.Title); + + var posts = blog.Posts.OrderBy(e => e.Title).ToList(); + + Assert.Equal(2, posts.Count); + + Assert.StartsWith("Baxter", posts[0].Title); + Assert.StartsWith("He", posts[0].Content); + + Assert.StartsWith("Golden", posts[1].Title); + Assert.StartsWith("Smaller", posts[1].Content); + + posts[0].Content += " He is just trying to make a living."; + + blog.AddPost(new Post("Olive has a TPLO", "Yes she does.")); + + var newBlog = context.Add(new Blog("Cats", 100)).Entity; + newBlog.AddPost(new Post("Baxter is a cat.", "With dog friends.")); + + context.SaveChanges(); + }, + context => + { + var blogs = context.Set().Include(e => e.Posts).OrderBy(e => e.Title).ToList(); + + Assert.Equal(2, blogs.Count); + + Assert.Equal("Cats", blogs[0].Title); + Assert.Equal("Puppies", blogs[1].Title); + + var posts = blogs[0].Posts.OrderBy(e => e.Title).ToList(); + + Assert.Equal(1, posts.Count); + + Assert.StartsWith("Baxter", posts[0].Title); + Assert.StartsWith("With dog", posts[0].Content); + + posts = blogs[1].Posts.OrderBy(e => e.Title).ToList(); + + Assert.Equal(3, posts.Count); + + Assert.StartsWith("Baxter", posts[0].Title); + Assert.EndsWith("living.", posts[0].Content); + + Assert.StartsWith("Golden", posts[1].Title); + Assert.StartsWith("Smaller", posts[1].Content); + + Assert.StartsWith("Olive", posts[2].Title); + Assert.StartsWith("Yes", posts[2].Content); + }); + } + + [Fact] + public virtual void Query_with_context_injected() + { + using (var context = CreateContext()) + { + Assert.Same(context, context.Set>().Single().Context); + Assert.Same(context, context.Set>().Single().Context); + Assert.Null(context.Set>().Single().Context); + } + + // Ensure new context instance is injected on repeated uses + using (var context = CreateContext()) + { + Assert.Same(context, context.Set>().Single().Context); + Assert.Same(context, context.Set>().Single().Context); + Assert.Null(context.Set>().Single().Context); + } + } + + [Fact] + public virtual void Query_with_loader_injected_for_reference() + { + using (var context = CreateContext()) + { + var post = context.Set().OrderBy(e => e.Id).First(); + + Assert.NotNull(post.LazyBlog); + Assert.Contains(post, post.LazyBlog.LazyPosts); + } + } + + [Fact] + public virtual void Query_with_loader_injected_for_collections() + { + using (var context = CreateContext()) + { + var blog = context.Set().Single(); + + Assert.Equal(2, blog.LazyPosts.Count()); + Assert.Same(blog, blog.LazyPosts.First().LazyBlog); + Assert.Same(blog, blog.LazyPosts.Skip(1).First().LazyBlog); + } + } + + [Fact] + public virtual void Query_with_POCO_loader_injected_for_reference() + { + using (var context = CreateContext()) + { + var post = context.Set().OrderBy(e => e.Id).First(); + + Assert.NotNull(post.LazyPocoBlog); + Assert.Contains(post, post.LazyPocoBlog.LazyPocoPosts); + } + } + + [Fact] + public virtual void Query_with_POCO_loader_injected_for_collections() + { + using (var context = CreateContext()) + { + var blog = context.Set().Single(); + + Assert.Equal(2, blog.LazyPocoPosts.Count()); + Assert.Same(blog, blog.LazyPocoPosts.First().LazyPocoBlog); + Assert.Same(blog, blog.LazyPocoPosts.Skip(1).First().LazyPocoBlog); + } + } + + protected class Blog + { + private int _blogId; + + private Blog( + int blogId, + string title, + int? monthlyRevenue) + { + _blogId = blogId; + Title = title; + MonthlyRevenue = monthlyRevenue; + } + + public Blog( + string title, + int? monthlyRevenue = null) + : this(0, title, monthlyRevenue) + { + } + + public string Title { get; } + public int? MonthlyRevenue { get; set; } + + public IEnumerable Posts { get; } = new List(); + + public void AddPost(Post post) + => ((List)Posts).Add(post); + } + + protected class Post + { + private int _id; + + private Post( + int id, + string title, + string content) + { + _id = id; + Title = title; + Content = content; + } + + public Post( + string title, + string content, + Blog blog = null) + : this(0, title, content) + { + Blog = blog; + } + + public string Title { get; } + public string Content { get; set; } + + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local + public Blog Blog { get; private set; } + } + + protected class HasContext + where TContext : DbContext + { + public HasContext() + { + } + + private HasContext(TContext context, int id) + { + Context = context; + Id = id; + } + + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local + public int Id { get; private set; } + public TContext Context { get; } + } + + protected class LazyBlog + { + private readonly ILazyLoader _loader; + private ICollection _lazyPosts = new List(); + + public LazyBlog() + { + } + + private LazyBlog(ILazyLoader loader) + { + _loader = loader; + } + + public int Id { get; set; } + + public void AddPost(LazyPost post) => _lazyPosts.Add(post); + + public IEnumerable LazyPosts => _loader.Load(this, ref _lazyPosts); + } + + protected class LazyPost + { + private readonly ILazyLoader _loader; + private LazyBlog _lazyBlog; + + public LazyPost() + { + } + + private LazyPost(ILazyLoader loader) + { + _loader = loader; + } + + public int Id { get; set; } + + public LazyBlog LazyBlog + { + get => _loader.Load(this, ref _lazyBlog); + set => _lazyBlog = value; + } + } + + protected class LazyPocoBlog + { + private readonly Action _loader; + private ICollection _lazyPocoPosts = new List(); + + public LazyPocoBlog() + { + } + + private LazyPocoBlog(Action lazyLoader) + { + _loader = lazyLoader; + } + + public int Id { get; set; } + + public void AddPost(LazyPocoPost post) => _lazyPocoPosts.Add(post); + + public IEnumerable LazyPocoPosts => _loader.Load(this, ref _lazyPocoPosts); + } + + protected class LazyPocoPost + { + private readonly Action _loader; + private LazyPocoBlog _lazyPocoBlog; + + public LazyPocoPost() + { + } + + private LazyPocoPost(Action lazyLoader) + { + _loader = lazyLoader; + } + + public int Id { get; set; } + + public LazyPocoBlog LazyPocoBlog + { + get => _loader.Load(this, ref _lazyPocoBlog); + set => _lazyPocoBlog = value; + } + } + + public class OtherContext : DbContext + { + } + + public class WithConstructorsContext : DbContext + { + public WithConstructorsContext(DbContextOptions options) + : base(options) + { + } + } + + public abstract class WithConstructorsFixtureBase : SharedStoreFixtureBase + { + protected override string StoreName { get; } = "WithConstructors"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.Entity( + b => + { + b.HasKey("_blogId"); + b.Property(e => e.Title); + }); + + modelBuilder.Entity( + b => + { + b.HasKey("_id"); + b.Property(e => e.Title); + }); + + modelBuilder.Entity>(); + modelBuilder.Entity>(); + modelBuilder.Entity>(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + } + + protected override void Seed(WithConstructorsContext context) + { + var blog = new Blog("Puppies"); + + var post1 = new Post( + "Golden Toasters Rock", + "Smaller than the Black Library Dog, and more chewy.", + blog); + + var post2 = new Post( + "Baxter is not a dog", + "He is a cat. Who eats dog food. And wags his tail.", + blog); + + context.AddRange(blog, post1, post2); + + context.AddRange( + new HasContext(), + new HasContext(), + new HasContext()); + + var lazyBlog = new LazyBlog(); + lazyBlog.AddPost(new LazyPost()); + lazyBlog.AddPost(new LazyPost()); + + context.Add(lazyBlog); + + var lazyPocoBlog = new LazyPocoBlog(); + lazyPocoBlog.AddPost(new LazyPocoPost()); + lazyPocoBlog.AddPost(new LazyPocoPost()); + + context.Add(lazyPocoBlog); + + context.SaveChanges(); + } + } + } +} diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs index f2c1d568226..a2252a01ef7 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Converters; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -93,7 +94,9 @@ public static ConventionSet Build() new SqlServerSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies())) .AddConventions( new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(sqlServerTypeMapper)) + new CoreConventionSetBuilderDependencies( + sqlServerTypeMapper, + new ConstructorBindingFactory())) .CreateConventionSet()); } } diff --git a/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs index 08a255fe376..fa6660f8482 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Converters; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -40,7 +41,10 @@ public static ConventionSet Build() new RelationalConventionSetBuilderDependencies(relationalTypeMapper, null, null)) .AddConventions( new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(relationalTypeMapper)).CreateConventionSet()); + new CoreConventionSetBuilderDependencies( + relationalTypeMapper, + new ConstructorBindingFactory())) + .CreateConventionSet()); } } } diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index dc85c117a6e..bd63218bec4 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -71,6 +71,18 @@ public ChangeTracker( /// public virtual bool AutoDetectChangesEnabled { get; set; } = true; + /// + /// + /// Gets or sets a value indicating whether navigation properties for tracked entities + /// will be loaded on first access. + /// + /// + /// The default value is true. However, lazy loading will only occur for navigation properties + /// of entities that have also been configured in the model for lazy loading. + /// + /// + public virtual bool LazyLoadingEnabled { get; set; } = true; + /// /// /// Gets or sets the tracking behavior for LINQ queries run against the context. Disabling change tracking diff --git a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs index 3a91e3adb47..9b81adce37c 100644 --- a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs @@ -35,7 +35,7 @@ public ArrayPropertyValues([NotNull] InternalEntityEntry internalEntry, [NotNull /// directly from your code. This API may change or be removed in future releases. /// public override object ToObject() - => MaterializerSource.GetMaterializer(EntityType)(new ValueBuffer(_values)); + => MaterializerSource.GetMaterializer(EntityType)(new ValueBuffer(_values), InternalEntry.StateManager.Context); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 628b402e9d5..c7dca5c40cd 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -143,7 +143,7 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity) { _trackingQueryMode = TrackingQueryMode.Multiple; - var entityType = _model.FindEntityType(entity.GetType()); + var entityType = _model.FindRuntimeEntityType(entity.GetType()); if (entityType == null) { if (_model.HasEntityTypeWithDefiningNavigation(entity.GetType())) @@ -199,7 +199,7 @@ public virtual InternalEntityEntry CreateEntry(IDictionary value } var valueBuffer = new ValueBuffer(valuesArray); - var entity = entityType.HasClrType() ? EntityMaterializerSource.GetMaterializer(entityType)(valueBuffer) : null; + var entity = entityType.HasClrType() ? EntityMaterializerSource.GetMaterializer(entityType)(valueBuffer, Context) : null; var entry = _internalEntityEntryFactory.Create(this, entityType, entity, valueBuffer); AddToReferenceMap(entry); @@ -271,7 +271,7 @@ public virtual InternalEntityEntry StartTrackingFromQuery( this, baseEntityType.ClrType == clrType ? baseEntityType - : _model.FindEntityType(clrType), + : _model.FindRuntimeEntityType(clrType), entity, valueBuffer); foreach (var key in baseEntityType.GetKeys()) @@ -509,7 +509,7 @@ public virtual void StopTracking(InternalEntityEntry entry) foreach (var keyValuePair in _referencedUntrackedEntities.Value.ToList()) { - var untrackedEntityType = _model.FindEntityType(keyValuePair.Key.GetType()); + var untrackedEntityType = _model.FindRuntimeEntityType(keyValuePair.Key.GetType()); if (navigations.Any(n => n.GetTargetType().IsAssignableFrom(untrackedEntityType)) || untrackedEntityType.GetNavigations().Any(n => n.GetTargetType().IsAssignableFrom(entityType))) { diff --git a/src/EFCore/ChangeTracking/LocalView.cs b/src/EFCore/ChangeTracking/LocalView.cs index 2f4cc7e0a26..442d78a914f 100644 --- a/src/EFCore/ChangeTracking/LocalView.cs +++ b/src/EFCore/ChangeTracking/LocalView.cs @@ -66,7 +66,7 @@ public class LocalView : ICollection, INotifyCollectionChanged public LocalView([NotNull] DbSet set) { _context = set.GetService().Context; - _context.CheckDisposed(); + CheckDisposed(); _stateManager = _context.GetDependencies().StateManager; @@ -76,6 +76,14 @@ public LocalView([NotNull] DbSet set) .Count(e => e.Entity is TEntity && e.EntityState != EntityState.Deleted); } + private void CheckDisposed() + { + if (_context.IsDisposed) + { + throw new ObjectDisposedException(_context.GetType().ShortDisplayName(), CoreStrings.ContextDisposed); + } + } + /// /// Returns an implementation that stays in sync with this collection. /// Use this for WPF data binding. @@ -172,7 +180,7 @@ private void ObservableCollectionChanged(object _, NotifyCollectionChangedEventA /// An enumerator for the collection. public virtual IEnumerator GetEnumerator() { - _context.CheckDisposed(); + CheckDisposed(); return _stateManager.Entries.Where(e => e.EntityState != EntityState.Deleted) .Select(e => e.Entity) @@ -204,7 +212,7 @@ public virtual void Add(TEntity item) // to Add it again since doing so would change its state to Added, which is probably not what // was wanted in this case. - _context.CheckDisposed(); + CheckDisposed(); var entry = _stateManager.GetOrCreateEntry(item); if (entry.EntityState == EntityState.Deleted @@ -245,7 +253,7 @@ public virtual void Add(TEntity item) /// public virtual void Clear() { - _context.CheckDisposed(); + CheckDisposed(); foreach (var entry in _stateManager.Entries .Where(e => e.Entity is TEntity && e.EntityState != EntityState.Deleted) @@ -263,7 +271,7 @@ public virtual void Clear() /// True if the entity is being tracked by the context and has not been marked as Deleted. public virtual bool Contains(TEntity item) { - _context.CheckDisposed(); + CheckDisposed(); var entry = _stateManager.TryGetEntry(item); @@ -278,7 +286,7 @@ public virtual bool Contains(TEntity item) /// The index into the array to start copying. public virtual void CopyTo(TEntity[] array, int arrayIndex) { - _context.CheckDisposed(); + CheckDisposed(); foreach (var entry in _stateManager.Entries) { @@ -307,7 +315,7 @@ public virtual void CopyTo(TEntity[] array, int arrayIndex) /// True if the entity was being tracked and was not already Deleted. public virtual bool Remove(TEntity item) { - _context.CheckDisposed(); + CheckDisposed(); var entry = _stateManager.TryGetEntry(item); if (entry != null diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index 6328bdacd09..c02ddb1b60c 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -278,7 +278,7 @@ private IDbContextDependencies DbContextDependencies => _dbContextDependencies ?? (_dbContextDependencies = InternalServiceProvider.GetRequiredService()); [DebuggerStepThrough] - internal void CheckDisposed() + private void CheckDisposed() { if (_disposed) { @@ -286,6 +286,11 @@ internal void CheckDisposed() } } + /// + /// Returns whether or not the context has been disposed. + /// + public virtual bool IsDisposed => _disposed; + /// /// /// Override this method to configure the database (and other options) to be used for this context. diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index c4a1e1dafb8..12b9bf690f8 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -64,7 +64,9 @@ private enum Id ServiceProviderCreated, ManyServiceProvidersCreatedWarning, ContextInitialized, - ExecutionStrategyRetrying + ExecutionStrategyRetrying, + LazyLoadOnDisposedContextWarning, + NavigationLazyLoading } private static readonly string _updatePrefix = DbLoggerCategory.Update.Name + "."; @@ -291,5 +293,31 @@ public static readonly EventId PossibleUnintendedReferenceComparisonWarning /// /// public static readonly EventId ExecutionStrategyRetrying = MakeInfraId(Id.ExecutionStrategyRetrying); + + /// + /// + /// A navigation property is being lazy-loaded. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId NavigationLazyLoading = MakeInfraId(Id.NavigationLazyLoading); + + /// + /// + /// An attempt was made to lazy-load a property after the DbContext had been disposed. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId LazyLoadOnDisposedContextWarning = MakeInfraId(Id.LazyLoadOnDisposedContextWarning); } } diff --git a/src/EFCore/Diagnostics/LazyLoadingEventData.cs b/src/EFCore/Diagnostics/LazyLoadingEventData.cs new file mode 100644 index 00000000000..7e2f702d200 --- /dev/null +++ b/src/EFCore/Diagnostics/LazyLoadingEventData.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Diagnostics +{ + /// + /// A event payload class for events from + /// + public class LazyLoadingEventData : DbContextEventData + { + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The current . + /// The entity instance on which lazy-loading was initiated. + /// The navigation property name of the relationship to be loaded. + public LazyLoadingEventData( + [NotNull] EventDefinitionBase eventDefinition, + [NotNull] Func messageGenerator, + [NotNull] DbContext context, + [NotNull] object entity, + [NotNull] string navigationPropertyName) + : base(eventDefinition, messageGenerator, context) + { + Entity = entity; + NavigationPropertyName = navigationPropertyName; + } + + /// + /// The entity instance on which lazy-loading was initiated. + /// + public virtual object Entity { get; } + + /// + /// The navigation property name of the relationship to be loaded. + /// + public virtual string NavigationPropertyName { get; } + } +} diff --git a/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs b/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs index 40b0be0daa4..925f5d3a5e5 100644 --- a/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs +++ b/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs @@ -587,5 +587,81 @@ private static string ExecutionStrategyRetrying(EventDefinitionBase definition, return d.GenerateMessage( (int)p.Delay.TotalMilliseconds, Environment.NewLine, p.ExceptionsEncountered[p.ExceptionsEncountered.Count - 1]); } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void LazyLoadOnDisposedContextWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] DbContext context, + [NotNull] object entityType, + [NotNull] string navigationName) + { + var definition = CoreStrings.LazyLoadOnDisposedContextWarning; + + // Checking for enabled here to avoid string formatting if not needed. + if (diagnostics.GetLogBehavior(definition.EventId, definition.Level) != WarningBehavior.Ignore) + { + definition.Log(diagnostics, navigationName, entityType.GetType().ShortDisplayName()); + } + + if (diagnostics.DiagnosticSource.IsEnabled(definition.EventId.Name)) + { + diagnostics.DiagnosticSource.Write( + definition.EventId.Name, + new LazyLoadingEventData( + definition, + LazyLoadOnDisposedContextWarning, + context, + entityType, + navigationName)); + } + } + + private static string LazyLoadOnDisposedContextWarning(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (LazyLoadingEventData)payload; + return d.GenerateMessage(p.NavigationPropertyName, p.Entity.GetType().ShortDisplayName()); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void NavigationLazyLoading( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] DbContext context, + [NotNull] object entityType, + [NotNull] string navigationName) + { + var definition = CoreStrings.NavigationLazyLoading; + + // Checking for enabled here to avoid string formatting if not needed. + if (diagnostics.GetLogBehavior(definition.EventId, definition.Level) != WarningBehavior.Ignore) + { + definition.Log(diagnostics, navigationName, entityType.GetType().ShortDisplayName()); + } + + if (diagnostics.DiagnosticSource.IsEnabled(definition.EventId.Name)) + { + diagnostics.DiagnosticSource.Write( + definition.EventId.Name, + new LazyLoadingEventData( + definition, + NavigationLazyLoading, + context, + entityType, + navigationName)); + } + } + + private static string NavigationLazyLoading(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (LazyLoadingEventData)payload; + return d.GenerateMessage(p.NavigationPropertyName, p.Entity.GetType().ShortDisplayName()); + } } } diff --git a/src/EFCore/Extensions/ModelExtensions.cs b/src/EFCore/Extensions/ModelExtensions.cs index 5e36588dae9..9f26dd54684 100644 --- a/src/EFCore/Extensions/ModelExtensions.cs +++ b/src/EFCore/Extensions/ModelExtensions.cs @@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore public static class ModelExtensions { /// - /// Gets the entity that maps the given entity class. Returns null if no entity type with the given name is found + /// Gets the entity that maps the given entity class. Returns null if no entity type with the given CLR type is found /// or the entity type has a defining navigation. /// /// The model to find the entity type in. @@ -26,6 +26,25 @@ public static class ModelExtensions public static IEntityType FindEntityType([NotNull] this IModel model, [NotNull] Type type) => Check.NotNull(model, nameof(model)).AsModel().FindEntityType(Check.NotNull(type, nameof(type))); + /// + /// Gets the entity that maps the given entity class, where the class may be a proxy derived from the + /// actual entity type. Returns null if no entity type with the given CLR type is found + /// or the entity type has a defining navigation. + /// + /// The model to find the entity type in. + /// The type to find the corresponding entity type for. + /// The entity type, or null if none if found. + public static IEntityType FindRuntimeEntityType([NotNull] this IModel model, [NotNull] Type type) + { + Check.NotNull(type, nameof(type)); + var realModel = Check.NotNull(model, nameof(model)).AsModel(); + + return realModel.FindEntityType(type) + ?? (type.BaseType == null + ? null + : realModel.FindEntityType(type.BaseType)); + } + /// /// Gets the entity type for the given type, defining navigation name /// and the defining entity type. Returns null if no matching entity type is found. diff --git a/src/EFCore/ILazyLoader.cs b/src/EFCore/ILazyLoader.cs new file mode 100644 index 00000000000..398e51aef79 --- /dev/null +++ b/src/EFCore/ILazyLoader.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore +{ + /// + /// A service that can be injected into entities to give them the capability + /// of loading navigation properties automatically the first time they are accessed. + /// + public interface ILazyLoader + { + /// + /// Loads a navigation property if it has not already been loaded. + /// + /// The entity on which the navigation property is located. + /// The navigation property name. + // ReSharper disable once AssignNullToNotNullAttribute + void Load([NotNull] object entity, [NotNull] [CallerMemberName] string navigationName = null); + } +} diff --git a/src/EFCore/Infrastructure/CoreOptionsExtension.cs b/src/EFCore/Infrastructure/CoreOptionsExtension.cs index 71ea740638e..35c6e04e914 100644 --- a/src/EFCore/Infrastructure/CoreOptionsExtension.cs +++ b/src/EFCore/Infrastructure/CoreOptionsExtension.cs @@ -38,8 +38,9 @@ public class CoreOptionsExtension : IDbContextOptionsExtension private int? _maxPoolSize; private long? _serviceProviderHash; private string _logFragment; - private WarningsConfiguration _warningsConfiguration = new WarningsConfiguration(); + private WarningsConfiguration _warningsConfiguration + = new WarningsConfiguration().TryWithExplicit(CoreEventId.LazyLoadOnDisposedContextWarning, WarningBehavior.Throw); /// /// Creates a new set of options with everything set to default values. /// diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index b554a5546d9..7e20162da35 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -89,6 +89,7 @@ public static readonly IDictionary CoreServices { typeof(IProjectionExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IDiagnosticsLogger<>), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IValueConverterSelector), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IConstructorBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IEntityGraphAttacher), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IKeyPropagator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(INavigationFixer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -124,6 +125,7 @@ public static readonly IDictionary CoreServices { typeof(IQueryContextFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IEntityQueryableExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IEntityQueryModelVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IEntityStateListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, { typeof(INavigationListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, { typeof(IKeyListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, @@ -259,6 +261,8 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd>(p => p.GetService); TryAdd(); TryAdd(); + TryAdd(); + TryAdd(); ServiceCollectionMap .TryAddSingleton(new DiagnosticListener(DbLoggerCategory.Name)); diff --git a/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs b/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs index c9ce84f8b27..8db57a3e89e 100644 --- a/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs +++ b/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs @@ -39,7 +39,6 @@ public sealed class ModelCustomizerDependencies /// the constructor at any point in this process. /// /// - // ReSharper disable once EmptyConstructor public ModelCustomizerDependencies([NotNull] IDbSetFinder setFinder) { Check.NotNull(setFinder, nameof(setFinder)); diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 36483863120..4db7ea2b06d 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -343,11 +343,16 @@ protected virtual void ValidateFieldMapping([NotNull] IModel model) foreach (var entityType in model.GetEntityTypes()) { + var constructorBinding = (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding]; + foreach (var propertyBase in entityType .GetDeclaredProperties() .Cast() .Concat(entityType.GetDeclaredNavigations()) - .Where(e => !e.IsShadowProperty)) + .Where( + e => !e.IsShadowProperty + && (constructorBinding == null + || constructorBinding.ParameterBindings.All(p => p.ConsumedProperty != e)))) { if (!propertyBase.TryGetMemberInfo( forConstruction: true, diff --git a/src/EFCore/Internal/InternalDbSet.cs b/src/EFCore/Internal/InternalDbSet.cs index 35f9a18a554..488665d1814 100644 --- a/src/EFCore/Internal/InternalDbSet.cs +++ b/src/EFCore/Internal/InternalDbSet.cs @@ -47,7 +47,10 @@ private IEntityType EntityType { get { - _context.CheckDisposed(); + if (_context.IsDisposed) + { + throw new ObjectDisposedException(_context.GetType().ShortDisplayName(), CoreStrings.ContextDisposed); + } if (_entityType != null) { diff --git a/src/EFCore/Internal/LazyLoader.cs b/src/EFCore/Internal/LazyLoader.cs new file mode 100644 index 00000000000..69b80f99e13 --- /dev/null +++ b/src/EFCore/Internal/LazyLoader.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class LazyLoader : ILazyLoader + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public LazyLoader( + [NotNull] ICurrentDbContext currentContext, + [NotNull] IDiagnosticsLogger logger) + { + Check.NotNull(currentContext, nameof(currentContext)); + Check.NotNull(logger, nameof(logger)); + + Context = currentContext.Context; + Logger = logger; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected virtual IDiagnosticsLogger Logger { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected virtual DbContext Context { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + // ReSharper disable once AssignNullToNotNullAttribute + public virtual void Load(object entity, [CallerMemberName] string navigationName = null) + { + Check.NotNull(entity, nameof(entity)); + Check.NotEmpty(navigationName, nameof(navigationName)); + + if (Context.IsDisposed) + { + Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName); + } + else if (Context.ChangeTracker.LazyLoadingEnabled) + { + var entry = Context.Entry(entity).Navigation(navigationName); + if (!entry.IsLoaded) + { + Logger.NavigationLazyLoading(Context, entity, navigationName); + + entry.Load(); + } + } + } + } +} diff --git a/src/EFCore/LazyLoaderExtensions.cs b/src/EFCore/LazyLoaderExtensions.cs new file mode 100644 index 00000000000..655fc0f3c4a --- /dev/null +++ b/src/EFCore/LazyLoaderExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Extension methods for the service that make it more + /// convenient to use from entity classes. + /// + public static class LazyLoaderExtensions + { + /// + /// Loads a navigation property if it has not already been loaded. + /// + /// The type of the navigation property. + /// The loader instance, which may be null. + /// The entity on which the navigation property is located. + /// A reference to the backing field for the navigation. + /// The navigation property name. + /// + /// The loaded navigation property value, or the navigation property value unchanged if the loader is null. + /// + public static TRelated Load( + [CanBeNull] this ILazyLoader loader, + [NotNull] object entity, + [CanBeNull] ref TRelated navigationField, + // ReSharper disable once AssignNullToNotNullAttribute + [NotNull] [CallerMemberName] string navigationName = null) + where TRelated : class + { + // ReSharper disable once AssignNullToNotNullAttribute + loader?.Load(entity, navigationName); + + return navigationField; + } + } +} diff --git a/src/EFCore/Metadata/Conventions/Internal/ConstructorBindingConvention.cs b/src/EFCore/Metadata/Conventions/Internal/ConstructorBindingConvention.cs new file mode 100644 index 00000000000..f19d8a51a7b --- /dev/null +++ b/src/EFCore/Metadata/Conventions/Internal/ConstructorBindingConvention.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ConstructorBindingConvention : IModelBuiltConvention + { + private readonly IConstructorBindingFactory _bindingFactory; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public ConstructorBindingConvention([NotNull] IConstructorBindingFactory bindingFactory) + => _bindingFactory = bindingFactory; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + if (entityType.ClrType != null + && !entityType.ClrType.IsAbstract) + { + var bindingFailures = new List>(); + + foreach (var constructor in entityType.ClrType.GetTypeInfo() + .DeclaredConstructors + .Where(c => !c.IsStatic) + .OrderByDescending(c => c.GetParameters().Length) + .ThenBy(c => string.Join("|", c.GetParameters().Select(p => p.GetType().Name)))) // Just to be deterministic + { + if (_bindingFactory.TryBindConstructor(entityType, constructor, out var binding, out var failures)) + { + entityType.Builder.HasAnnotation( + CoreAnnotationNames.ConstructorBinding, + binding, + ConfigurationSource.Convention); + + break; + } + + bindingFailures.Add(failures); + } + + if (entityType[CoreAnnotationNames.ConstructorBinding] == null) + { + throw new InvalidOperationException( + CoreStrings.ConstructorNotFound( + entityType.DisplayName(), + string.Join("', '", bindingFailures.SelectMany(f => f).Select(f => f.Name)))); + } + } + } + + return modelBuilder; + } + } +} diff --git a/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilder.cs index f60c882c3df..0568c83ee9a 100644 --- a/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilder.cs @@ -121,6 +121,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.ModelBuiltConventions.Add(new RelationshipValidationConvention()); conventionSet.ModelBuiltConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.ModelBuiltConventions.Add(new ChangeTrackingStrategyConvention()); + conventionSet.ModelBuiltConventions.Add(new ConstructorBindingConvention(Dependencies.ConstructorBindingFactory)); conventionSet.NavigationAddedConventions.Add(backingFieldConvention); conventionSet.NavigationAddedConventions.Add(new RequiredNavigationAttributeConvention()); diff --git a/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilderDependencies.cs b/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilderDependencies.cs index ee642028f03..8a28ba08595 100644 --- a/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilderDependencies.cs +++ b/src/EFCore/Metadata/Conventions/Internal/CoreConventionSetBuilderDependencies.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -47,11 +48,15 @@ public sealed class CoreConventionSetBuilderDependencies /// directly from your code. This API may change or be removed in future releases. /// /// - public CoreConventionSetBuilderDependencies([NotNull] ITypeMapper typeMapper) + public CoreConventionSetBuilderDependencies( + [NotNull] ITypeMapper typeMapper, + [NotNull] IConstructorBindingFactory constructorBindingFactory) { Check.NotNull(typeMapper, nameof(typeMapper)); + Check.NotNull(constructorBindingFactory, nameof(constructorBindingFactory)); TypeMapper = typeMapper; + ConstructorBindingFactory = constructorBindingFactory; } /// @@ -60,12 +65,26 @@ public CoreConventionSetBuilderDependencies([NotNull] ITypeMapper typeMapper) /// public ITypeMapper TypeMapper { get; } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IConstructorBindingFactory ConstructorBindingFactory { get; } + /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public CoreConventionSetBuilderDependencies With([NotNull] ITypeMapper typeMapper) - => new CoreConventionSetBuilderDependencies(typeMapper); + => new CoreConventionSetBuilderDependencies(typeMapper, ConstructorBindingFactory); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public CoreConventionSetBuilderDependencies With([NotNull] IConstructorBindingFactory constructorBindingFactory) + => new CoreConventionSetBuilderDependencies(TypeMapper, constructorBindingFactory); } } diff --git a/src/EFCore/Metadata/Internal/ConstructorBinding.cs b/src/EFCore/Metadata/Internal/ConstructorBinding.cs new file mode 100644 index 00000000000..8734857cca7 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ConstructorBinding.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public abstract class ConstructorBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected ConstructorBinding( + [NotNull] IReadOnlyList parameterBindings) + { + ParameterBindings = parameterBindings; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public abstract Expression CreateConstructorExpression(ParameterBindingInfo bindingInfo); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IReadOnlyList ParameterBindings { get; } + } +} diff --git a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs new file mode 100644 index 00000000000..10ed410a691 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ConstructorBindingFactory : IConstructorBindingFactory + { + private readonly IList _factories + = new List + { + new PropertyParameterBindingFactory(), + new ContextParameterBindingFactory(), + new LazyLoaderParameterBindingFactory(), + new EntityTypeParameterBindingFactory() + }; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual bool TryBindConstructor( + IMutableEntityType entityType, + ConstructorInfo constructor, + out ConstructorBinding binding, + out IEnumerable failedBindings) + { + IEnumerable<(ParameterInfo Parameter, ParameterBinding Binding)> bindings + = constructor.GetParameters().Select( + p => (p, _factories.Select(f => f.TryBindParameter(entityType, p)) + .FirstOrDefault(b => b != null))) + .ToList(); + + if (bindings.Any(b => b.Binding == null)) + { + failedBindings = bindings.Where(b => b.Binding == null).Select(b => b.Parameter); + binding = null; + + return false; + } + + failedBindings = null; + binding = new DirectConstructorBinding(constructor, bindings.Select(b => b.Binding).ToList()); + + return true; + } + } +} diff --git a/src/EFCore/Metadata/Internal/ContextParameterBinding.cs b/src/EFCore/Metadata/Internal/ContextParameterBinding.cs new file mode 100644 index 00000000000..3920f19ae2a --- /dev/null +++ b/src/EFCore/Metadata/Internal/ContextParameterBinding.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ContextParameterBinding : ParameterBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public ContextParameterBinding([NotNull] Type contextType) + : base(null) + { + ContextType = contextType; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Type ContextType { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression BindToParameter(ParameterBindingInfo bindingInfo) + => Expression.TypeAs(bindingInfo.ContextExpression, ContextType); + } +} diff --git a/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs new file mode 100644 index 00000000000..0127992ea67 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ContextParameterBindingFactory : ParameterBindingFactory + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter) + => typeof(DbContext).IsAssignableFrom(parameter.ParameterType) + ? new ContextParameterBinding(parameter.ParameterType) + : null; + } +} diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index 1b1d3f6322d..dddb8700a1f 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -45,6 +45,12 @@ public static class CoreAnnotationNames /// public const string OwnedTypesAnnotation = "OwnedTypes"; + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public const string ConstructorBinding = "ConstructorBinding"; + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. diff --git a/src/EFCore/Metadata/Internal/DirectConstructorBinding.cs b/src/EFCore/Metadata/Internal/DirectConstructorBinding.cs new file mode 100644 index 00000000000..83e267e3334 --- /dev/null +++ b/src/EFCore/Metadata/Internal/DirectConstructorBinding.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class DirectConstructorBinding : ConstructorBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public DirectConstructorBinding( + [NotNull] ConstructorInfo constructor, + [NotNull] IReadOnlyList parameterBindings) + : base(parameterBindings) + { + Constructor = constructor; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual ConstructorInfo Constructor { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression CreateConstructorExpression(ParameterBindingInfo bindingInfo) + => Expression.New( + Constructor, + ParameterBindings.Select(b => b.BindToParameter(bindingInfo))); + } +} diff --git a/src/EFCore/Metadata/Internal/EntityMaterializerSource.cs b/src/EFCore/Metadata/Internal/EntityMaterializerSource.cs index 48174fb2ff5..cf4e8e75f20 100644 --- a/src/EFCore/Metadata/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Metadata/Internal/EntityMaterializerSource.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Threading; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -18,7 +19,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// public class EntityMaterializerSource : IEntityMaterializerSource { - private ConcurrentDictionary> _materializers; + private ConcurrentDictionary> _materializers; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -119,16 +120,9 @@ public virtual Expression CreateReadValueCallExpression(Expression valueBuffer, public virtual Expression CreateMaterializeExpression( IEntityType entityType, Expression valueBufferExpression, + Expression contextExpression, int[] indexMap = null) { - if (entityType is IEntityMaterializer materializer) - { - return Expression.Call( - Expression.Constant(materializer), - ((Func)materializer.CreateEntity).GetMethodInfo(), - valueBufferExpression); - } - if (!entityType.HasClrType()) { throw new InvalidOperationException(CoreStrings.NoClrType(entityType.DisplayName())); @@ -139,25 +133,40 @@ public virtual Expression CreateMaterializeExpression( throw new InvalidOperationException(CoreStrings.CannotMaterializeAbstractType(entityType)); } - var constructorInfo = entityType.ClrType.GetDeclaredConstructor(null); + var constructorBinding = (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding]; - if (constructorInfo == null) + if (constructorBinding == null) { - throw new InvalidOperationException(CoreStrings.NoParameterlessConstructor(entityType.DisplayName())); + var constructorInfo = entityType.ClrType.GetDeclaredConstructor(null); + + if (constructorInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoParameterlessConstructor(entityType.DisplayName())); + } + + constructorBinding = new DirectConstructorBinding(constructorInfo, new ParameterBinding[0]); } var instanceVariable = Expression.Variable(entityType.ClrType, "instance"); + var bindingInfo = new ParameterBindingInfo( + entityType, + valueBufferExpression, + contextExpression, + indexMap); + var blockExpressions = new List { Expression.Assign( instanceVariable, - Expression.New(constructorInfo)) + constructorBinding.CreateConstructorExpression(bindingInfo)) }; blockExpressions.AddRange( - from property in entityType.GetProperties().Where(p => !p.IsShadowProperty) + from property in entityType.GetProperties().Where( + p => !p.IsShadowProperty + && constructorBinding.ParameterBindings.All(b => b.ConsumedProperty != p)) let targetMember = Expression.MakeMemberAccess( instanceVariable, property.GetMemberInfo(forConstruction: true, forSet: true)) @@ -175,23 +184,26 @@ from property in entityType.GetProperties().Where(p => !p.IsShadowProperty) return Expression.Block(new[] { instanceVariable }, blockExpressions); } - private ConcurrentDictionary> Materializers - => _materializers - ?? (_materializers = new ConcurrentDictionary>()); + private ConcurrentDictionary> Materializers + => LazyInitializer.EnsureInitialized( + ref _materializers, + () => new ConcurrentDictionary>()); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Func GetMaterializer(IEntityType entityType) + public virtual Func GetMaterializer(IEntityType entityType) => Materializers.GetOrAdd( entityType, e => { var valueBufferParameter = Expression.Parameter(typeof(ValueBuffer), "values"); + var contextParameter = Expression.Parameter(typeof(DbContext), "context"); - return Expression.Lambda>( - CreateMaterializeExpression(e, valueBufferParameter), - valueBufferParameter) + return Expression.Lambda>( + CreateMaterializeExpression(e, valueBufferParameter, contextParameter), + valueBufferParameter, + contextParameter) .Compile(); }); } diff --git a/src/EFCore/Metadata/Internal/EntityTypeParameterBinding.cs b/src/EFCore/Metadata/Internal/EntityTypeParameterBinding.cs new file mode 100644 index 00000000000..54bcd8afbe0 --- /dev/null +++ b/src/EFCore/Metadata/Internal/EntityTypeParameterBinding.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class EntityTypeParameterBinding : ParameterBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public EntityTypeParameterBinding() + : base(null) + { + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression BindToParameter(ParameterBindingInfo bindingInfo) + => Expression.Constant(bindingInfo.EnityType, typeof(IEntityType)); + } +} diff --git a/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs new file mode 100644 index 00000000000..c9ece0e8f06 --- /dev/null +++ b/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class EntityTypeParameterBindingFactory : ParameterBindingFactory + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter) + => parameter.ParameterType == typeof(IEntityType) + ? new EntityTypeParameterBinding() + : null; + } +} diff --git a/src/EFCore/Metadata/Internal/FactoryMethodConstructorBinding.cs b/src/EFCore/Metadata/Internal/FactoryMethodConstructorBinding.cs new file mode 100644 index 00000000000..12360ddff40 --- /dev/null +++ b/src/EFCore/Metadata/Internal/FactoryMethodConstructorBinding.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class FactoryMethodConstructorBinding : ConstructorBinding + { + private readonly object _factoryInstance; + private readonly MethodInfo _factoryMethod; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public FactoryMethodConstructorBinding( + [NotNull] MethodInfo factoryMethod, + [NotNull] IReadOnlyList parameterBindings) + : base(parameterBindings) + { + _factoryMethod = factoryMethod; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public FactoryMethodConstructorBinding( + [NotNull] object factoryInstance, + [NotNull] MethodInfo factoryMethod, + [NotNull] IReadOnlyList parameterBindings) + : this(factoryMethod, parameterBindings) + { + _factoryInstance = factoryInstance; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression CreateConstructorExpression(ParameterBindingInfo bindingInfo) + { + var arguments = ParameterBindings.Select(b => b.BindToParameter(bindingInfo)); + + Expression expression + = _factoryInstance == null + ? Expression.Call( + _factoryMethod, + arguments) + : Expression.Call( + Expression.Constant(_factoryInstance), + _factoryMethod, + arguments); + + if (_factoryMethod.ReturnType != bindingInfo.EnityType.ClrType) + { + expression = Expression.Convert(expression, bindingInfo.EnityType.ClrType); + } + + return expression; + } + } +} diff --git a/src/EFCore/Metadata/Internal/IConstructorBindingFactory.cs b/src/EFCore/Metadata/Internal/IConstructorBindingFactory.cs new file mode 100644 index 00000000000..82ef4b59950 --- /dev/null +++ b/src/EFCore/Metadata/Internal/IConstructorBindingFactory.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public interface IConstructorBindingFactory + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + bool TryBindConstructor( + [NotNull] IMutableEntityType entityType, + [NotNull] ConstructorInfo constructor, + [CanBeNull] out ConstructorBinding binding, + [CanBeNull] out IEnumerable failedBindings); + } +} diff --git a/src/EFCore/Metadata/Internal/IEntityMaterializerSource.cs b/src/EFCore/Metadata/Internal/IEntityMaterializerSource.cs index 2ad06c6469c..cee45b52c49 100644 --- a/src/EFCore/Metadata/Internal/IEntityMaterializerSource.cs +++ b/src/EFCore/Metadata/Internal/IEntityMaterializerSource.cs @@ -38,12 +38,13 @@ Expression CreateReadValueExpression( Expression CreateMaterializeExpression( [NotNull] IEntityType entityType, [NotNull] Expression valueBufferExpression, + [NotNull] Expression contextExpression, [CanBeNull] int[] indexMap = null); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Func GetMaterializer([NotNull] IEntityType entityType); + Func GetMaterializer([NotNull] IEntityType entityType); } } diff --git a/src/EFCore/Metadata/Internal/LazyLoaderParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/LazyLoaderParameterBindingFactory.cs new file mode 100644 index 00000000000..3bee0f3c259 --- /dev/null +++ b/src/EFCore/Metadata/Internal/LazyLoaderParameterBindingFactory.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class LazyLoaderParameterBindingFactory : ParameterBindingFactory + { + private static readonly MethodInfo _loadMethod = typeof(ILazyLoader).GetMethod(nameof(ILazyLoader.Load)); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter) + { + if (parameter.ParameterType == typeof(ILazyLoader)) + { + EnsureFieldAccess(enityType); + + return new ServiceParameterBinding(typeof(ILazyLoader)); + } + + if (parameter.ParameterType == typeof(Action) + && parameter.Name.Equals("lazyLoader", StringComparison.OrdinalIgnoreCase)) + { + EnsureFieldAccess(enityType); + + return new ServiceMethodParameterBinding(typeof(ILazyLoader), _loadMethod); + } + + return null; + } + + private static void EnsureFieldAccess(IMutableEntityType enityType) + { + foreach (var navigation in enityType.GetNavigations()) + { + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + } + } + } +} diff --git a/src/EFCore/Metadata/Internal/ParameterBinding.cs b/src/EFCore/Metadata/Internal/ParameterBinding.cs new file mode 100644 index 00000000000..36426fb21f5 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ParameterBinding.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public abstract class ParameterBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected ParameterBinding([CanBeNull] IPropertyBase consumedProperty) + { + ConsumedProperty = consumedProperty; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IPropertyBase ConsumedProperty { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public abstract Expression BindToParameter(ParameterBindingInfo bindingInfo); + } +} diff --git a/src/EFCore/Metadata/Internal/IEntityMaterializer.cs b/src/EFCore/Metadata/Internal/ParameterBindingFactory.cs similarity index 73% rename from src/EFCore/Metadata/Internal/IEntityMaterializer.cs rename to src/EFCore/Metadata/Internal/ParameterBindingFactory.cs index 787ce9183a6..a59c10506d0 100644 --- a/src/EFCore/Metadata/Internal/IEntityMaterializer.cs +++ b/src/EFCore/Metadata/Internal/ParameterBindingFactory.cs @@ -1,7 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Storage; +using System.Reflection; +using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Metadata.Internal { @@ -9,12 +10,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public interface IEntityMaterializer + public abstract class ParameterBindingFactory { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - object CreateEntity(ValueBuffer valueBuffer); + public abstract ParameterBinding TryBindParameter( + [NotNull] IMutableEntityType enityType, + [NotNull] ParameterInfo parameter); } } diff --git a/src/EFCore/Metadata/Internal/ParameterBindingInfo.cs b/src/EFCore/Metadata/Internal/ParameterBindingInfo.cs new file mode 100644 index 00000000000..ac3c0ba9768 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ParameterBindingInfo.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public struct ParameterBindingInfo + { + private readonly int[] _indexMap; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public ParameterBindingInfo( + [NotNull] IEntityType enityType, + [NotNull] Expression valueBufferExpression, + [NotNull] Expression contextExpression, + [CanBeNull] int[] indexMap) + { + _indexMap = indexMap; + EnityType = enityType; + ValueBufferExpression = valueBufferExpression; + ContextExpression = contextExpression; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IEntityType EnityType { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public Expression ValueBufferExpression { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public Expression ContextExpression { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public int GetValueBufferIndex([NotNull] IPropertyBase property) + => _indexMap?[property.GetIndex()] ?? property.GetIndex(); + } +} diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index 21ec1718763..aa3a7acec92 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using JetBrains.Annotations; @@ -154,8 +155,7 @@ public static bool TryGetMemberInfo( { if (!isCollectionNav) { - errorMessage = CoreStrings.NoBackingField( - propertyBase.Name, propertyBase.DeclaringType.DisplayName(), nameof(PropertyAccessMode)); + errorMessage = GetNoFieldErrorMessage(propertyBase); return false; } return true; @@ -219,10 +219,10 @@ public static bool TryGetMemberInfo( if (!forSet || !isCollectionNav) { - errorMessage = CoreStrings.NoBackingField( - propertyBase.Name, propertyBase.DeclaringType.DisplayName(), nameof(PropertyAccessMode)); + errorMessage = GetNoFieldErrorMessage(propertyBase); return false; } + return true; } @@ -272,6 +272,20 @@ public static bool TryGetMemberInfo( return true; } + private static string GetNoFieldErrorMessage(IPropertyBase propertyBase) + { + var constructorBinding = (ConstructorBinding)propertyBase.DeclaringType[CoreAnnotationNames.ConstructorBinding]; + + return constructorBinding != null + && constructorBinding.ParameterBindings + .OfType() + .Any(b => b.ServiceType == typeof(ILazyLoader)) + ? CoreStrings.NoBackingFieldLazyLoading( + propertyBase.Name, propertyBase.DeclaringType.DisplayName()) + : CoreStrings.NoBackingField( + propertyBase.Name, propertyBase.DeclaringType.DisplayName(), nameof(PropertyAccessMode)); + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. diff --git a/src/EFCore/Metadata/Internal/PropertyParameterBinding.cs b/src/EFCore/Metadata/Internal/PropertyParameterBinding.cs new file mode 100644 index 00000000000..3ef41be2489 --- /dev/null +++ b/src/EFCore/Metadata/Internal/PropertyParameterBinding.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class PropertyParameterBinding : ParameterBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public PropertyParameterBinding([NotNull] IProperty consumedProperty) + : base(consumedProperty) + { + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression BindToParameter(ParameterBindingInfo bindingInfo) + => Expression.Call( + EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(ConsumedProperty.ClrType), + bindingInfo.ValueBufferExpression, + Expression.Constant(bindingInfo.GetValueBufferIndex(ConsumedProperty)), + Expression.Constant(ConsumedProperty, typeof(IPropertyBase))); + } +} diff --git a/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs new file mode 100644 index 00000000000..85a62084de1 --- /dev/null +++ b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class PropertyParameterBindingFactory : ParameterBindingFactory + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter) + { + var property = enityType.GetProperties().FirstOrDefault( + p => p.ClrType == parameter.ParameterType + && MatchName(parameter, p)); + + return property != null + ? new PropertyParameterBinding(property) + : null; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected virtual bool MatchName([NotNull] ParameterInfo parameter, [NotNull] IProperty property) + { + var propertyName = property.Name; + + if (propertyName.StartsWith("m_", StringComparison.OrdinalIgnoreCase)) + { + propertyName = propertyName.Substring(2); + } + + return propertyName.Trim('_').Equals(parameter.Name, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/EFCore/Metadata/Internal/ServiceMethodParameterBinding.cs b/src/EFCore/Metadata/Internal/ServiceMethodParameterBinding.cs new file mode 100644 index 00000000000..5db914d500f --- /dev/null +++ b/src/EFCore/Metadata/Internal/ServiceMethodParameterBinding.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ServiceMethodParameterBinding : ServiceParameterBinding + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public ServiceMethodParameterBinding([NotNull] Type serviceType, [NotNull] MethodInfo method) + : base(serviceType) + { + Method = method; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual MethodInfo Method { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression BindToParameter(ParameterBindingInfo bindingInfo) + { + var parameters = Method.GetParameters().Select( + (p, i) => Expression.Parameter(p.ParameterType, "param" + i)).ToArray(); + + var serviceVariable = Expression.Variable(ServiceType, "service"); + + return Expression.Block( + new[] { serviceVariable }, + new List + { + Expression.Assign( + serviceVariable, + base.BindToParameter(bindingInfo)), + Expression.Lambda( + Expression.Call( + serviceVariable, + Method, + parameters), + parameters) + }); + } + } +} diff --git a/src/EFCore/Metadata/Internal/ServiceParameterBinding.cs b/src/EFCore/Metadata/Internal/ServiceParameterBinding.cs new file mode 100644 index 00000000000..620ae79851d --- /dev/null +++ b/src/EFCore/Metadata/Internal/ServiceParameterBinding.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ServiceParameterBinding : ParameterBinding + { + private static readonly MethodInfo _getServiceMethod + = typeof(AccessorExtensions).GetMethod(nameof(AccessorExtensions.GetService)); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public ServiceParameterBinding([NotNull] Type serviceType) + : base(null) + { + ServiceType = serviceType; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Type ServiceType { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Expression BindToParameter(ParameterBindingInfo bindingInfo) + => Expression.Call( + _getServiceMethod.MakeGenericMethod(ServiceType), + Expression.Convert(bindingInfo.ContextExpression, typeof(IInfrastructure))); + } +} diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 1d0e77b6990..23561b3c022 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -271,13 +271,21 @@ public static string BadBackingFieldType([CanBeNull] object field, [CanBeNull] o field, fieldType, entityType, property, propertyType); /// - /// No field was found backing property '{property}' of entity type '{entity}'. Either configure a backing field or use a different '{pam}'. + /// No field was found backing property '{property}' of entity type '{entity}'. Either name the backing field so that it is picked up by convention, configure the backing field to use, or use a different '{pam}'. /// public static string NoBackingField([CanBeNull] object property, [CanBeNull] object entity, [CanBeNull] object pam) => string.Format( GetString("NoBackingField", nameof(property), nameof(entity), nameof(pam)), property, entity, pam); + /// + /// No field was found backing property '{property}' of entity type '{entity}'. Lazy-loaded navigation properties must have backing fields. Either name the backing field so that it is picked up by convention or configure the backing field to use. + /// + public static string NoBackingFieldLazyLoading([CanBeNull] object property, [CanBeNull] object entity) + => string.Format( + GetString("NoBackingFieldLazyLoading", nameof(property), nameof(entity)), + property, entity); + /// /// No backing field could be found for property '{property}' of entity type '{entity}' and the property does not have a setter. /// @@ -1910,6 +1918,14 @@ public static string SeedDatumDerivedType([CanBeNull] object entityType, [CanBeN GetString("SeedDatumDerivedType", nameof(entityType), nameof(derivedType)), entityType, derivedType); + /// + /// No suitable constructor found for entity type '{entityType}'. The following parameters could not be bound to properties of the entity: '{parameters}'. + /// + public static string ConstructorNotFound([CanBeNull] object entityType, [CanBeNull] object parameters) + => string.Format( + GetString("ConstructorNotFound", nameof(entityType), nameof(parameters)), + entityType, parameters); + /// /// The type '{entityType}' cannot be marked as owned because a non-owned entity type with the same name already exists. /// @@ -1936,6 +1952,30 @@ public static readonly EventDefinition LogExecutionStrat CoreEventId.ExecutionStrategyRetrying, _resourceManager.GetString("LogExecutionStrategyRetrying"))); + /// + /// Navigation property '{navigation}' of entity type '{entityType}' is being lazy-loaded. + /// + public static readonly EventDefinition NavigationLazyLoading + = new EventDefinition( + CoreEventId.NavigationLazyLoading, + LogLevel.Information, + LoggerMessage.Define( + LogLevel.Information, + CoreEventId.NavigationLazyLoading, + _resourceManager.GetString("NavigationLazyLoading"))); + + /// + /// An attempt was made to lazy-load navigation property '{navigation}' on entity type '{entityType}' after the associated DbContext was disposed. + /// + public static readonly EventDefinition LazyLoadOnDisposedContextWarning + = new EventDefinition( + CoreEventId.LazyLoadOnDisposedContextWarning, + LogLevel.Warning, + LoggerMessage.Define( + LogLevel.Warning, + CoreEventId.LazyLoadOnDisposedContextWarning, + _resourceManager.GetString("LazyLoadOnDisposedContextWarning"))); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 77bdfe3562e..16a70d618a6 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -214,7 +214,10 @@ The specified field '{field}' of type '{fieldType}' cannot be used for the property '{entityType}.{property}' of type '{propertyType}'. Only backing fields of types that are assignable from the property type can be used. - No field was found backing property '{property}' of entity type '{entity}'. Either configure a backing field or use a different '{pam}'. + No field was found backing property '{property}' of entity type '{entity}'. Either name the backing field so that it is picked up by convention, configure the backing field to use, or use a different '{pam}'. + + + No field was found backing property '{property}' of entity type '{entity}'. Lazy-loaded navigation properties must have backing fields. Either name the backing field so that it is picked up by convention or configure the backing field to use. No backing field could be found for property '{property}' of entity type '{entity}' and the property does not have a setter. @@ -832,6 +835,9 @@ The seed entity for entity type '{entityType}' cannot be added because the value provided is of a derived type '{derivedType}'. Add the derived seed entities to the corresponding entity type. + + No suitable constructor found for entity type '{entityType}'. The following parameters could not be bound to properties of the entity: '{parameters}'. + The type '{entityType}' cannot be marked as owned because a non-owned entity type with the same name already exists. @@ -842,4 +848,12 @@ A transient exception has been encountered during execution and the operation will be retried after {delay}ms.{newline}{error} Information CoreEventId.ExecutionStrategyRetrying int string Exception + + Navigation property '{navigation}' of entity type '{entityType}' is being lazy-loaded. + Information CoreEventId.NavigationLazyLoading DbContext object string + + + An attempt was made to lazy-load navigation property '{navigation}' on entity type '{entityType}' after the associated DbContext was disposed. + Warning CoreEventId.LazyLoadOnDisposedContextWarning DbContext object string + diff --git a/src/EFCore/Query/EntityLoadInfo.cs b/src/EFCore/Query/EntityLoadInfo.cs index 99311b85896..bd0d030e2b4 100644 --- a/src/EFCore/Query/EntityLoadInfo.cs +++ b/src/EFCore/Query/EntityLoadInfo.cs @@ -21,8 +21,9 @@ namespace Microsoft.EntityFrameworkCore.Query /// public struct EntityLoadInfo { - private readonly Func _materializer; + private readonly Func _materializer; private readonly Dictionary _typeIndexMap; + private readonly DbContext _context; /// /// Initializes a new instance of the struct. @@ -30,6 +31,7 @@ public struct EntityLoadInfo /// The row of data that represents this entity. /// The method to materialize the data into an entity instance. /// Dictionary containing mapping from property indexes to values in ValueBuffer. + [Obsolete("Use the constructor that also takes a DbContext.")] public EntityLoadInfo( ValueBuffer valueBuffer, [NotNull] Func materializer, @@ -39,6 +41,30 @@ public EntityLoadInfo( Debug.Assert(materializer != null); ValueBuffer = valueBuffer; + _context = null; + _materializer = (v, c) => materializer(v); + _typeIndexMap = typeIndexMap; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The row of data that represents this entity. + /// The current for injecting services into the entity. + /// The method to materialize the data into an entity instance. + /// Dictionary containing mapping from property indexes to values in ValueBuffer. + public EntityLoadInfo( + ValueBuffer valueBuffer, + [NotNull] DbContext context, + [NotNull] Func materializer, + [CanBeNull] Dictionary typeIndexMap = null) + { + // hot path + Debug.Assert(materializer != null); + Debug.Assert(context != null); + + ValueBuffer = valueBuffer; + _context = context; _materializer = materializer; _typeIndexMap = typeIndexMap; } @@ -52,7 +78,7 @@ public EntityLoadInfo( /// Materializes the data into an entity instance. /// /// The entity instance. - public object Materialize() => _materializer(ValueBuffer); + public object Materialize() => _materializer(ValueBuffer, _context); /// /// Creates a new ValueBuffer containing only the values needed for entities of a given type. diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index a9205bc2685..c8c30f6750d 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -235,12 +235,7 @@ public void Snapshot_with_default_values_are_round_tripped() new CSharpMigrationOperationGeneratorDependencies(codeHelper)), new CSharpSnapshotGenerator(new CSharpSnapshotGeneratorDependencies(codeHelper)))); - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) - .CreateConventionSet()); - + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); modelBuilder.Entity( eb => { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs index c02228789eb..e9dfdcae609 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs @@ -7,6 +7,7 @@ using System.Transactions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.Logging; using Xunit; @@ -106,7 +107,7 @@ public void Throws_when_warning_as_error_specific() public void Logs_by_default_for_ignored_includes() { var messages = new List(); - using (var context = new WarningAsErrorContext(messages)) + using (var context = new WarningAsErrorContext(new FakeLoggerFactory(messages))) { context.WarningAsErrorEntities.Include(e => e.Nav).OrderBy(e => e.Id).Select(e => e.Id).ToList(); @@ -129,6 +130,94 @@ public void Ignored_includes_can_be_configured_to_throw() } } + [Fact] + public void Throws_by_default_for_lazy_load_with_disposed_context() + { + var messages = new List(); + var loggerFactory = new FakeLoggerFactory(messages); + + using (var context = new WarningAsErrorContext(loggerFactory)) + { + context.Add(new WarningAsErrorEntity { Nav = new IncludedEntity() }); + context.SaveChanges(); + } + + WarningAsErrorEntity entity; + + using (var context = new WarningAsErrorContext(loggerFactory)) + { + entity = context.WarningAsErrorEntities.OrderBy(e => e.Id).First(); + } + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + CoreEventId.LazyLoadOnDisposedContextWarning.ToString(), + CoreStrings.LazyLoadOnDisposedContextWarning.GenerateMessage("Nav", "WarningAsErrorEntity")), + Assert.Throws( + () => entity.Nav).Message); + } + + [Fact] + public void Lazy_load_with_disposed_context_can_be_configured_to_log() + { + var messages = new List(); + var loggerFactory = new FakeLoggerFactory(messages); + + using (var context = new WarningAsErrorContext( + loggerFactory, + CoreEventId.LazyLoadOnDisposedContextWarning)) + { + context.Add(new WarningAsErrorEntity { Nav = new IncludedEntity() }); + context.SaveChanges(); + } + + WarningAsErrorEntity entity; + + using (var context = new WarningAsErrorContext( + loggerFactory, + CoreEventId.LazyLoadOnDisposedContextWarning)) + { + entity = context.WarningAsErrorEntities.OrderBy(e => e.Id).First(); + } + + Assert.Null(entity.Nav); + + Assert.Contains( + CoreStrings.LazyLoadOnDisposedContextWarning.GenerateMessage("Nav", "WarningAsErrorEntity"), + messages); + } + + [Fact] + public void Lazy_loading_is_logged_only_when_actually_loading() + { + var messages = new List(); + var loggerFactory = new FakeLoggerFactory(messages); + + using (var context = new WarningAsErrorContext(loggerFactory)) + { + context.Add(new WarningAsErrorEntity { Nav = new IncludedEntity() }); + context.SaveChanges(); + } + + using (var context = new WarningAsErrorContext(loggerFactory)) + { + var entity = context.WarningAsErrorEntities.OrderBy(e => e.Id).First(); + + messages.Clear(); + Assert.NotNull(entity.Nav); + + Assert.Contains( + CoreStrings.NavigationLazyLoading.GenerateMessage("Nav", "WarningAsErrorEntity"), + messages); + + messages.Clear(); + Assert.NotNull(entity.Nav); + Assert.DoesNotContain( + CoreStrings.NavigationLazyLoading.GenerateMessage("Nav", "WarningAsErrorEntity"), + messages); + } + } + [Fact] public void No_throw_when_event_id_not_registered() { @@ -141,12 +230,12 @@ public void No_throw_when_event_id_not_registered() private class WarningAsErrorContext : DbContext { - private readonly IList _sink; + private readonly FakeLoggerFactory _sink; private readonly EventId? _toLog; private readonly EventId? _toThrow; public WarningAsErrorContext( - IList sink = null, + FakeLoggerFactory sink = null, EventId? toLog = null, EventId? toThrow = null) { @@ -159,7 +248,7 @@ public WarningAsErrorContext( protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseLoggerFactory(new FakeLoggerFactory(_sink)) + .UseLoggerFactory(_sink ?? new FakeLoggerFactory(null)) .UseInMemoryDatabase(nameof(WarningAsErrorContext)).ConfigureWarnings( c => { @@ -180,7 +269,23 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) private class WarningAsErrorEntity { - public IncludedEntity Nav { get; set; } + private readonly Action _loader; + private IncludedEntity _nav; + + public WarningAsErrorEntity() + { + } + + private WarningAsErrorEntity(Action lazyLoader) + { + _loader = lazyLoader; + } + + public IncludedEntity Nav + { + get => _loader.Load(this, ref _nav); + set => _nav = value; + } public string Id { get; set; } } diff --git a/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs new file mode 100644 index 00000000000..c57134563c0 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class WithConstructorsInMemoryTest : WithConstructorsTestBase + { + public WithConstructorsInMemoryTest(WithConstructorsInMemoryFixture fixture) + : base(fixture) + { + } + + public override void Query_and_update_using_constructors_with_property_parameters() + { + base.Query_and_update_using_constructors_with_property_parameters(); + + Fixture.Reseed(); + } + + public class WithConstructorsInMemoryFixture : WithConstructorsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(w => w.Log(InMemoryEventId.TransactionIgnoredWarning)); + } + } +} diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs index 46a0cd63431..d75aaa3efb0 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/DiscriminatorConventionTest.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -165,9 +164,7 @@ private static InternalEntityTypeBuilder CreateInternalEntityTypeBuilder() var modelBuilder = new InternalModelBuilder( new Model( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) + TestServiceFactory.Instance.Create() .CreateConventionSet())); return modelBuilder.Entity(typeof(T), ConfigurationSource.Explicit); diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs index 8d4b6c9f15a..39707ebb828 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs @@ -18,9 +18,7 @@ public static ConventionSet Build() new RelationalConventionSetBuilderDependencies( TestServiceFactory.Instance.Create(), null, null)) .AddConventions( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) + TestServiceFactory.Instance.Create() .CreateConventionSet()); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs index 98456fdbb8e..89157ac356f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs @@ -18,6 +18,402 @@ public LoadSqlServerTest(LoadSqlServerFixture fixture) fixture.TestSqlLoggerFactory.Clear(); } + public override void Lazy_load_collection(EntityState state) + { + base.Lazy_load_collection(state); + + Assert.Equal( + @"@__get_Item_0='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [Child] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_principal(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_dependent(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent(state); + + Assert.Equal( + @"@__get_Item_0='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [Single] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityState state) + { + base.Lazy_load_one_to_one_PK_to_PK_reference_to_principal(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityState state) + { + base.Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id] +FROM [SinglePkToPk] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_null_FK(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_principal_null_FK(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_null_FK(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_collection_not_found(EntityState state) + { + base.Lazy_load_collection_not_found(state); + + Assert.Equal( + @"@__get_Item_0='767' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [Child] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_not_found(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_not_found(state); + + Assert.Equal( + @"@__get_Item_0='787' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_principal_not_found(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_not_found(state); + + Assert.Equal( + @"@__get_Item_0='787' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_dependent_not_found(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent_not_found(state); + + Assert.Equal( + @"@__get_Item_0='767' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [Single] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_collection_already_loaded(EntityState state) + { + base.Lazy_load_collection_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_principal_already_loaded(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already_loaded(EntityState state) + { + base.Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already_loaded(EntityState state) + { + base.Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already_loaded(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_many_to_one_reference_to_principal_alternate_key(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_alternate_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[AlternateId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_principal_alternate_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_alternate_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[AlternateId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_dependent_alternate_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent_alternate_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) + +SELECT [e].[Id], [e].[ParentId] +FROM [SingleAk] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_null_FK_alternate_key(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_null_FK_alternate_key(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_principal_null_FK_alternate_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_null_FK_alternate_key(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_collection_shadow_fk(EntityState state) + { + base.Lazy_load_collection_shadow_fk(state); + + Assert.Equal( + @"@__get_Item_0='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [ChildShadowFk] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_shadow_fk(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_shadow_fk(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_principal_shadow_fk(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_shadow_fk(state); + + Assert.Equal( + @"@__get_Item_0='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE [e].[Id] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_dependent_shadow_fk(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent_shadow_fk(state); + + Assert.Equal( + @"@__get_Item_0='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentId] +FROM [SingleShadowFk] AS [e] +WHERE [e].[ParentId] = @__get_Item_0", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_fk(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_fk(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_collection_composite_key(EntityState state) + { + base.Lazy_load_collection_composite_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) +@__get_Item_1='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentAlternateId], [e].[ParentId] +FROM [ChildCompositeKey] AS [e] +WHERE ([e].[ParentAlternateId] = @__get_Item_0) AND ([e].[ParentId] = @__get_Item_1)", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_composite_key(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_composite_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) +@__get_Item_1='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE ([e].[AlternateId] = @__get_Item_0) AND ([e].[Id] = @__get_Item_1)", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_principal_composite_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_composite_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) +@__get_Item_1='707' + +SELECT [e].[Id], [e].[AlternateId] +FROM [Parent] AS [e] +WHERE ([e].[AlternateId] = @__get_Item_0) AND ([e].[Id] = @__get_Item_1)", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_one_to_one_reference_to_dependent_composite_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_dependent_composite_key(state); + + Assert.Equal( + @"@__get_Item_0='Root' (Size = 450) +@__get_Item_1='707' (Nullable = true) + +SELECT [e].[Id], [e].[ParentAlternateId], [e].[ParentId] +FROM [SingleCompositeKey] AS [e] +WHERE ([e].[ParentAlternateId] = @__get_Item_0) AND ([e].[ParentId] = @__get_Item_1)", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Lazy_load_many_to_one_reference_to_principal_null_FK_composite_key(EntityState state) + { + base.Lazy_load_many_to_one_reference_to_principal_null_FK_composite_key(state); + + Assert.Equal("", Sql); + } + + public override void Lazy_load_one_to_one_reference_to_principal_null_FK_composite_key(EntityState state) + { + base.Lazy_load_one_to_one_reference_to_principal_null_FK_composite_key(state); + + Assert.Equal("", Sql); + } + public override async Task Load_collection(EntityState state, bool async) { await base.Load_collection(state, async); diff --git a/test/EFCore.SqlServer.FunctionalTests/WithConstructorsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/WithConstructorsSqlServerTest.cs new file mode 100644 index 00000000000..9f0daee2d15 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/WithConstructorsSqlServerTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class WithConstructorsSqlServerTest : WithConstructorsTestBase + { + public WithConstructorsSqlServerTest(WithConstructorsSqlServerFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class WithConstructorsSqlServerFixture : WithConstructorsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + } + } +} diff --git a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs index 1fd9bc15b3a..e2c499d0c6a 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs @@ -47,10 +47,7 @@ public override void Detects_duplicate_columns_in_derived_types_with_different_t public override void Detects_incompatible_shared_columns_with_shared_table() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); modelBuilder.Entity().HasOne().WithOne().IsRequired().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id); modelBuilder.Entity().Property(a => a.P0).HasColumnType("someInt"); @@ -147,10 +144,7 @@ public virtual void Passes_for_incompatible_indexes_within_hierarchy_when_one_na [Fact] public virtual void Detects_incompatible_momory_optimized_shared_table() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); modelBuilder.Entity().HasOne().WithOne().IsRequired().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id); modelBuilder.Entity().ToTable("Table").ForSqlServerIsMemoryOptimized(); @@ -164,11 +158,7 @@ public virtual void Detects_incompatible_momory_optimized_shared_table() [Fact] public virtual void Throws_for_unsupported_data_types() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); - + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); modelBuilder.Entity().Property(e => e.Name).HasColumnType("nvarchar"); Assert.Equal( diff --git a/test/EFCore.Sqlite.FunctionalTests/WithConstructorsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/WithConstructorsSqliteTest.cs new file mode 100644 index 00000000000..2ceaaacbd39 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/WithConstructorsSqliteTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class WithConstructorsSqliteTest : WithConstructorsTestBase + { + public WithConstructorsSqliteTest(WithConstructorsSqliteFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class WithConstructorsSqliteFixture : WithConstructorsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + } + } +} diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs index d8626b06d11..ec09fbd0192 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs @@ -22,9 +22,7 @@ public SqliteMigrationAnnotationProviderTest() { _modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) + TestServiceFactory.Instance.Create() .CreateConventionSet()); _provider = new SqliteMigrationsAnnotationProvider(new MigrationsAnnotationProviderDependencies()); diff --git a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs index c6abbe5ad8a..9d4b18037d9 100644 --- a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs @@ -20,9 +20,7 @@ public class SqliteModelValidatorTest : RelationalModelValidatorTest public override void Detects_duplicate_column_names() { var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); + TestServiceFactory.Instance.Create().CreateConventionSet()); GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").Metadata); @@ -54,9 +52,7 @@ public override void Detects_duplicate_column_names_within_hierarchy_with_differ public override void Detects_incompatible_shared_columns_with_shared_table() { var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); + TestServiceFactory.Instance.Create().CreateConventionSet()); modelBuilder.Entity().HasOne().WithOne().IsRequired().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id); modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt"); diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index d221dcd5be1..13410308a6c 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -1345,14 +1345,14 @@ public void TrackGraph_does_not_call_DetectChanges() } } - [Fact] // Issue #743 - public void Throws_when_instance_of_unmapped_derived_type_is_used() + [Fact] + public void Does_not_throw_when_instance_of_unmapped_derived_type_is_used() { using (var context = new EarlyLearningCenter()) { - Assert.Equal( - CoreStrings.EntityTypeNotFound(typeof(SpecialProduct).Name), - Assert.Throws(() => context.Add(new SpecialProduct())).Message); + Assert.Same( + context.Model.FindEntityType(typeof(Product)), + context.Add(new SpecialProduct()).Metadata); } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs index 7bbf8315c39..d119f74a8ee 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs @@ -771,14 +771,15 @@ public void Can_get_all_dependent_entries() Assert.Empty(stateManager.GetDependents(categoryEntry4, fk).ToArray()); } - [Fact] // Issue #743 - public void Throws_when_instance_of_unmapped_derived_type_is_used() + [Fact] + public void Does_not_throws_when_instance_of_unmapped_derived_type_is_used() { var model = BuildModel(); var stateManager = CreateStateManager(model); - Assert.Equal( - CoreStrings.EntityTypeNotFound(typeof(SpecialProduct).Name), - Assert.Throws(() => stateManager.GetOrCreateEntry(new SpecialProduct())).Message); + + var entry = stateManager.GetOrCreateEntry(new SpecialProduct()); + + Assert.Same(model.FindEntityType(typeof(Product)), entry.EntityType); } private static IStateManager CreateStateManager(IModel model) @@ -820,11 +821,7 @@ private class Dogegory private static IMutableModel BuildModel() { - var builder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); - + var builder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var model = builder.Model; builder.Entity().HasOne().WithOne() diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs index 0cbc8fd31a4..e6616b9019b 100644 --- a/test/EFCore.Tests/DbContextTest.cs +++ b/test/EFCore.Tests/DbContextTest.cs @@ -621,7 +621,7 @@ public async void It_throws_object_disposed_exception() await Assert.ThrowsAsync(() => context.FindAsync(typeof(Random), 77)); var methodCount = typeof(DbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Count(); - var expectedMethodCount = 40; + var expectedMethodCount = 41; Assert.True( methodCount == expectedMethodCount, userMessage: $"Expected {expectedMethodCount} methods on DbContext but found {methodCount}. " + @@ -631,7 +631,13 @@ public async void It_throws_object_disposed_exception() Assert.Throws(() => context.ChangeTracker); Assert.Throws(() => context.Model); - var expectedProperties = new List { "ChangeTracker", "Database", "Model" }; + var expectedProperties = new List + { + nameof(DbContext.ChangeTracker), + nameof(DbContext.Database), + nameof(DbContext.IsDisposed), + nameof(DbContext.Model) + }; Assert.True( expectedProperties.SequenceEqual( diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs index d6b7e4d46dc..1939a6e9c89 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs @@ -24,9 +24,7 @@ public virtual IModel Can_build_a_model_with_default_conventions_without_DI() } protected virtual ConventionSet GetConventionSet() - => new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet(); + => TestServiceFactory.Instance.Create().CreateConventionSet(); [Table("ProductTable")] protected class Product diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/CascadeDeleteConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/CascadeDeleteConventionTest.cs index c9fbfe82eb8..499cf412bb7 100644 --- a/test/EFCore.Tests/Metadata/Conventions/Internal/CascadeDeleteConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/CascadeDeleteConventionTest.cs @@ -127,9 +127,6 @@ private class Post private static ModelBuilder CreateModelBuilder() => new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) - .CreateConventionSet()); + TestServiceFactory.Instance.Create().CreateConventionSet()); } } diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/ConstructorBindingConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/ConstructorBindingConventionTest.cs new file mode 100644 index 00000000000..c3770fad006 --- /dev/null +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/ConstructorBindingConventionTest.cs @@ -0,0 +1,375 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal +{ + public class ConstructorBindingConventionTest + { + [Fact] + public void Can_bind_parameterless_constructor() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + Assert.Empty(constructorBinding.Constructor.GetParameters()); + Assert.Empty(constructorBinding.ParameterBindings); + } + + private class BlogParameterless : Blog + { + } + + [Fact] + public void Binds_to_most_parameters_that_resolve() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(3, parameters.Length); + Assert.Equal(3, bindings.Count); + + Assert.Equal("title", parameters[0].Name); + Assert.Equal("shadow", parameters[1].Name); + Assert.Equal("id", parameters[2].Name); + + Assert.Equal("Title", bindings[0].ConsumedProperty.Name); + Assert.Equal("Shadow", bindings[1].ConsumedProperty.Name); + Assert.Equal("Id", bindings[2].ConsumedProperty.Name); + } + + private class BlogSeveral : Blog + { + public BlogSeveral() + { + } + + public BlogSeveral(string title, int id) + { + } + + public BlogSeveral(string title, Guid? shadow, int id) + { + } + + public BlogSeveral(string title, Guid? shadow, bool dummy, int id) + { + } + } + + [Fact] + public void Binds_to_partial_set_of_parameters_that_resolve() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(2, parameters.Length); + Assert.Equal(2, bindings.Count); + + Assert.Equal("content", parameters[0].Name); + Assert.Equal("follows", parameters[1].Name); + + Assert.Equal("_content", bindings[0].ConsumedProperty.Name); + Assert.Equal("m_follows", bindings[1].ConsumedProperty.Name); + } + + private class BlogWierdScience : Blog + { + public BlogWierdScience(string content, int follows) + { + } + } + + [Fact] + public void Binds_to_context() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(2, parameters.Length); + Assert.Equal(2, bindings.Count); + + Assert.Equal("id", parameters[0].Name); + Assert.Equal("context", parameters[1].Name); + + Assert.IsType(bindings[0]); + Assert.Equal("Id", bindings[0].ConsumedProperty.Name); + + Assert.IsType(bindings[1]); + Assert.Null(bindings[1].ConsumedProperty); + Assert.Same(typeof(DbContext), ((ContextParameterBinding)bindings[1]).ContextType); + } + + private class BlogWithContext : Blog + { + public BlogWithContext(int id, DbContext context) + { + } + } + + [Fact] + public void Binds_to_context_typed() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(1, parameters.Length); + Assert.Equal(1, bindings.Count); + + Assert.Equal("context", parameters[0].Name); + + Assert.IsType(bindings[0]); + Assert.Null(bindings[0].ConsumedProperty); + Assert.Same(typeof(TypedContext), ((ContextParameterBinding)bindings[0]).ContextType); + } + + private class BlogWithTypedContext : Blog + { + public BlogWithTypedContext(TypedContext context) + { + } + } + + [Fact] + public void Binds_to_ILazyLoader() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(1, parameters.Length); + Assert.Equal(1, bindings.Count); + + Assert.Equal("loader", parameters[0].Name); + + Assert.IsType(bindings[0]); + Assert.Null(bindings[0].ConsumedProperty); + Assert.Same(typeof(ILazyLoader), ((ServiceParameterBinding)bindings[0]).ServiceType); + } + + private class BlogWithLazyLoader : Blog + { + public BlogWithLazyLoader(ILazyLoader loader) + { + } + } + + [Fact] + public void Binds_to_delegate_parameter_called_lazyLoader() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(1, parameters.Length); + Assert.Equal(1, bindings.Count); + + Assert.Equal("lazyLoader", parameters[0].Name); + + Assert.IsType(bindings[0]); + Assert.Null(bindings[0].ConsumedProperty); + Assert.Same(typeof(ILazyLoader), ((ServiceMethodParameterBinding)bindings[0]).ServiceType); + } + + private class BlogWithLazyLoaderMethod : Blog + { + public BlogWithLazyLoaderMethod(Action lazyLoader) + { + } + } + + [Fact] + public void Binds_to_IEntityType() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(1, parameters.Length); + Assert.Equal(1, bindings.Count); + + Assert.Equal("entityType", parameters[0].Name); + + Assert.IsType(bindings[0]); + Assert.Null(bindings[0].ConsumedProperty); + } + + private class BlogWithEntityType : Blog + { + public BlogWithEntityType(IEntityType entityType) + { + } + } + + [Fact] + public void Does_not_bind_to_delegate_parameter_not_called_lazyLoader() + { + var constructorBinding = GetBinding(); + + Assert.NotNull(constructorBinding); + + var parameters = constructorBinding.Constructor.GetParameters(); + var bindings = constructorBinding.ParameterBindings; + + Assert.Equal(0, parameters.Length); + Assert.Equal(0, bindings.Count); + } + + private class BlogWithOtherMethod : Blog + { + public BlogWithOtherMethod() + { + } + + public BlogWithOtherMethod(Action loader) + { + } + } + + private class TypedContext : DbContext + { + } + + [Fact] + public void Throws_if_no_usable_constructor() + { + Assert.Equal( + CoreStrings.ConstructorNotFound(nameof(BlogNone), "dummy', 'notTitle', 'did"), + Assert.Throws(() => GetBinding()).Message); + } + + private class BlogNone : Blog + { + public BlogNone(string title, int did) + { + } + + public BlogNone(string notTitle, Guid? shadow, int id) + { + } + + public BlogNone(string title, Guid? shadow, bool dummy, int id) + { + } + } + + [Fact] + public void Throws_if_no_usable_constructor_due_to_bad_type() + { + Assert.Equal( + CoreStrings.ConstructorNotFound(nameof(BlogBadType), "shadow"), + Assert.Throws(() => GetBinding()).Message); + } + + private class BlogBadType : Blog + { + public BlogBadType(Guid shadow, int id) + { + } + } + + [Fact] + public void Throws_in_validation_if_field_not_found() + { + using (var context = new NoFieldContext()) + { + Assert.Equal( + CoreStrings.NoBackingFieldLazyLoading("NoFieldRelated", "NoField"), + Assert.Throws(() => context.Model).Message); + } + } + + private class NoFieldContext : DbContext + { + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString()); + + public DbSet NoFields { get; } + public DbSet NoFieldRelateds { get; } + } + + private class NoField + { + private readonly Action _loader; + private ICollection _hidden_noFieldRelated; + public int Id { get; set; } + + public NoField(Action lazyLoader) + { + _loader = lazyLoader; + } + + public ICollection NoFieldRelated + { + get => _loader.Load(this, ref _hidden_noFieldRelated); + set => _hidden_noFieldRelated = value; + } + } + + private class NoFieldRelated + { + public int Id { get; set; } + public NoField NoField { get; set; } + } + + private static DirectConstructorBinding GetBinding() + { + var convention = TestServiceFactory.Instance.Create(); + + var entityType = new Model().AddEntityType(typeof(TEntity)); + entityType.AddProperty(nameof(Blog.Id), typeof(int)); + entityType.AddProperty(nameof(Blog.Title), typeof(string)); + entityType.AddProperty(nameof(Blog._content), typeof(string)); + entityType.AddProperty(nameof(Blog.m_follows), typeof(int)); + entityType.AddProperty("Shadow", typeof(Guid?)); + + convention.Apply(entityType.Model.Builder); + + return (DirectConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding]; + } + + private abstract class Blog + { +#pragma warning disable 649 + public string _content; + + // ReSharper disable once InconsistentNaming + public int m_follows; +#pragma warning restore 649 + + public int Id { get; set; } + public string Title { get; set; } + } + } +} diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/EntityTypeAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/EntityTypeAttributeConventionTest.cs index 9b3723c9b71..a87ff1deb0a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/Internal/EntityTypeAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/EntityTypeAttributeConventionTest.cs @@ -42,9 +42,7 @@ public void NotMappedAttribute_does_not_override_configuration_from_explicit_sou public void NotMappedAttribute_ignores_entityTypes_with_conventional_builder() { var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())) + TestServiceFactory.Instance.Create() .CreateConventionSet()); modelBuilder.Entity(); diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/ForeignKeyIndexConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/ForeignKeyIndexConventionTest.cs index 3a07936dbe1..0880eabae5b 100644 --- a/test/EFCore.Tests/Metadata/Conventions/Internal/ForeignKeyIndexConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/ForeignKeyIndexConventionTest.cs @@ -14,9 +14,8 @@ public void Does_not_override_foreign_key_index_uniqueness_when_referenced_key_c { var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())).CreateConventionSet()); + TestServiceFactory.Instance.Create() + .CreateConventionSet()); var principalTypeBuilder = modelBuilder.Entity(); var dependentTypeBuilder = modelBuilder.Entity(); diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/NavigationAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/NavigationAttributeConventionTest.cs index 93a8820900d..c49882d9784 100644 --- a/test/EFCore.Tests/Metadata/Conventions/Internal/NavigationAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/NavigationAttributeConventionTest.cs @@ -69,10 +69,7 @@ public void NotMappedAttribute_does_not_override_configuration_from_explicit_sou [Fact] public void NotMappedAttribute_ignores_navigation_with_conventional_builder() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); - + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var model = modelBuilder.Model; modelBuilder.Entity(); @@ -180,10 +177,7 @@ public void RequiredAttribute_does_not_set_is_required_for_navigation_to_depende [Fact] public void RequiredAttribute_sets_is_required_with_conventional_builder() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); - + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var model = (Model)modelBuilder.Model; modelBuilder.Entity(); @@ -549,10 +543,7 @@ public void ForeignKeyAttribute_throws_when_same_set_of_properties_are_pointed_b [Fact] public void Navigation_attribute_convention_runs_for_private_property() { - var modelBuilder = new ModelBuilder( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); - + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var referenceBuilder = modelBuilder.Entity().HasOne(typeof(Post), "Post").WithOne(); Assert.False(referenceBuilder.Metadata.Properties.First().IsNullable); diff --git a/test/EFCore.Tests/Metadata/Conventions/Internal/PropertyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/Internal/PropertyAttributeConventionTest.cs index 4f6498e2ca1..4e87dd6b0e3 100644 --- a/test/EFCore.Tests/Metadata/Conventions/Internal/PropertyAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/Internal/PropertyAttributeConventionTest.cs @@ -49,7 +49,7 @@ public void ConcurrencyCheckAttribute_does_not_override_configuration_from_expli [Fact] public void ConcurrencyCheckAttribute_sets_concurrency_token_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.True(entityTypeBuilder.Property(e => e.RowVersion).Metadata.IsConcurrencyToken); @@ -58,7 +58,7 @@ public void ConcurrencyCheckAttribute_sets_concurrency_token_with_conventional_b [Fact] public void ConcurrencyCheckAttribute_on_field_sets_concurrency_token_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.True(entityTypeBuilder.Property(nameof(F.RowVersion)).Metadata.IsConcurrencyToken); @@ -99,7 +99,7 @@ public void DatabaseGeneratedAttribute_does_not_override_configuration_from_expl [Fact] public void DatabaseGeneratedAttribute_sets_store_generated_pattern_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(ValueGenerated.OnAddOrUpdate, entityTypeBuilder.Property(e => e.Id).Metadata.ValueGenerated); @@ -108,7 +108,7 @@ public void DatabaseGeneratedAttribute_sets_store_generated_pattern_with_convent [Fact] public void DatabaseGeneratedAttribute_in_field_sets_store_generated_pattern_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(ValueGenerated.OnAddOrUpdate, entityTypeBuilder.Property(nameof(F.Id)).Metadata.ValueGenerated); @@ -229,7 +229,7 @@ public void KeyAttribute_allows_composite_key_with_inheritence() [Fact] public void KeyAttribute_on_field_sets_primary_key() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); entityTypeBuilder.Property(nameof(F.MyPrimaryKey)); @@ -271,7 +271,7 @@ public void MaxLengthAttribute_does_not_override_configuration_from_explicit_sou [Fact] public void MaxLengthAttribute_sets_max_length_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(10, entityTypeBuilder.Property(e => e.MaxLengthProperty).Metadata.GetMaxLength()); @@ -280,7 +280,7 @@ public void MaxLengthAttribute_sets_max_length_with_conventional_builder() [Fact] public void MaxLengthAttribute_on_field_sets_max_length_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(10, entityTypeBuilder.Property(nameof(F.MaxLengthProperty)).Metadata.GetMaxLength()); @@ -315,7 +315,7 @@ public void NotMappedAttribute_does_not_override_configuration_from_explicit_sou [Fact] public void NotMappedAttribute_ignores_property_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.False(entityTypeBuilder.Metadata.GetProperties().Any(p => p.Name == "IgnoredProperty")); @@ -324,7 +324,7 @@ public void NotMappedAttribute_ignores_property_with_conventional_builder() [Fact] public void NotMappedAttribute_on_field_does_not_ignore_property_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); entityTypeBuilder.Property(nameof(F.IgnoredProperty)); @@ -378,7 +378,7 @@ public void RequiredAttribute_does_not_override_configuration_from_explicit_sour [Fact] public void RequiredAttribute_sets_is_nullable_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.False(entityTypeBuilder.Property(e => e.Name).Metadata.IsNullable); @@ -387,7 +387,7 @@ public void RequiredAttribute_sets_is_nullable_with_conventional_builder() [Fact] public void RequiredAttribute_on_field_sets_is_nullable_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.False(entityTypeBuilder.Property(nameof(F.Name)).Metadata.IsNullable); @@ -428,7 +428,7 @@ public void StringLengthAttribute_does_not_override_configuration_from_explicit_ [Fact] public void StringLengthAttribute_sets_max_length_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(20, entityTypeBuilder.Property(e => e.StringLengthProperty).Metadata.GetMaxLength()); @@ -437,7 +437,7 @@ public void StringLengthAttribute_sets_max_length_with_conventional_builder() [Fact] public void StringLengthAttribute_on_field_sets_max_length_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(20, entityTypeBuilder.Property(nameof(F.StringLengthProperty)).Metadata.GetMaxLength()); @@ -482,7 +482,7 @@ public void TimestampAttribute_does_not_override_configuration_from_explicit_sou [Fact] public void TimestampAttribute_sets_concurrency_token_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(ValueGenerated.OnAddOrUpdate, entityTypeBuilder.Property(e => e.Timestamp).Metadata.ValueGenerated); @@ -492,7 +492,7 @@ public void TimestampAttribute_sets_concurrency_token_with_conventional_builder( [Fact] public void TimestampAttribute_on_field_sets_concurrency_token_with_conventional_builder() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var entityTypeBuilder = modelBuilder.Entity(); Assert.Equal(ValueGenerated.OnAddOrUpdate, entityTypeBuilder.Property(nameof(F.Timestamp)).Metadata.ValueGenerated); @@ -504,7 +504,7 @@ public void TimestampAttribute_on_field_sets_concurrency_token_with_conventional [Fact] public void Property_attribute_convention_runs_for_private_property() { - var modelBuilder = new ModelBuilder(new CoreConventionSetBuilder(new CoreConventionSetBuilderDependencies(CreateTypeMapper())).CreateConventionSet()); + var modelBuilder = new ModelBuilder(TestServiceFactory.Instance.Create().CreateConventionSet()); var propertyBuilder = modelBuilder.Entity().Property("PrivateProperty"); Assert.False(propertyBuilder.Metadata.IsNullable); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs index b2ef9627dc2..377edc57713 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; using Xunit; @@ -19,39 +19,109 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal public class EntityMaterializerSourceTest { [Fact] - public void Delegate_from_entity_type_is_returned_if_it_implements_IEntityMaterializer() + public void Can_create_materializer_for_entity_with_constructor_properties() { - Assert.Equal("Bazinga!", GetMaterializer(new EntityMaterializerSource(), new FakeType())(ValueBuffer.Empty)); + var entityType = CreateEntityType(); + + entityType[CoreAnnotationNames.ConstructorBinding] + = new DirectConstructorBinding( + typeof(SomeEntity).GetTypeInfo().DeclaredConstructors.Single(c => c.GetParameters().Length == 2), + new List + { + new PropertyParameterBinding(entityType.FindProperty(nameof(SomeEntity.Id))), + new PropertyParameterBinding(entityType.FindProperty(nameof(SomeEntity.Goo))), + } + ); + + var factory = GetMaterializer(new EntityMaterializerSource(), entityType); + + var gu = Guid.NewGuid(); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), null); + + Assert.Equal(77, entity.Id); + Assert.Equal("Fu", entity.Foo); + Assert.Equal(gu, entity.Goo); + Assert.Equal(SomeEnum.EnumValue, entity.Enum); + Assert.Equal(SomeEnum.EnumValue, entity.MaybeEnum); + + Assert.False(entity.FactoryUsed); + Assert.True(entity.ParameterizedConstructorUsed); + Assert.False(entity.IdSetterCalled); + Assert.False(entity.GooSetterCalled); } - private class FakeType : IEntityType, IEntityMaterializer + [Fact] + public void Can_create_materializer_for_entity_with_factory_method() { - public object CreateEntity(ValueBuffer valueBuffer) => "Bazinga!"; - - public object this[string name] => throw new NotImplementedException(); - public IAnnotation FindAnnotation(string name) => throw new NotImplementedException(); - public IEnumerable GetAnnotations() => throw new NotImplementedException(); - public IModel Model { get; } - public string Name { get; } - public Type ClrType { get; } - public IEntityType BaseType { get; } - public string DefiningNavigationName { get; } - public IEntityType DefiningEntityType { get; } - public LambdaExpression QueryFilter { get; } - public IKey FindPrimaryKey() => throw new NotImplementedException(); - public IKey FindKey(IReadOnlyList properties) => throw new NotImplementedException(); - public IEnumerable GetKeys() => throw new NotImplementedException(); - public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) => throw new NotImplementedException(); - public IEnumerable GetForeignKeys() => throw new NotImplementedException(); - public IIndex FindIndex(IReadOnlyList properties) => throw new NotImplementedException(); - public IEnumerable GetIndexes() => throw new NotImplementedException(); - public IProperty FindProperty(string name) => throw new NotImplementedException(); - public IEnumerable GetProperties() => throw new NotImplementedException(); - public IEnumerable> GetSeedData() => throw new NotImplementedException(); + var entityType = CreateEntityType(); + + entityType[CoreAnnotationNames.ConstructorBinding] + = new FactoryMethodConstructorBinding( + typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod("Factory"), + new List + { + new PropertyParameterBinding(entityType.FindProperty(nameof(SomeEntity.Id))), + new PropertyParameterBinding(entityType.FindProperty(nameof(SomeEntity.Goo))) + } + ); + + var factory = GetMaterializer(new EntityMaterializerSource(), entityType); + + var gu = Guid.NewGuid(); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), null); + + Assert.Equal(77, entity.Id); + Assert.Equal("Fu", entity.Foo); + Assert.Equal(gu, entity.Goo); + Assert.Equal(SomeEnum.EnumValue, entity.Enum); + Assert.Equal(SomeEnum.EnumValue, entity.MaybeEnum); + + Assert.True(entity.FactoryUsed); + Assert.True(entity.ParameterizedConstructorUsed); + Assert.False(entity.IdSetterCalled); + Assert.False(entity.GooSetterCalled); } [Fact] - public void Can_create_materializer_for_entity_with_auto_properties() + public void Can_create_materializer_for_entity_with_instance_factory_method() + { + var entityType = CreateEntityType(); + + entityType[CoreAnnotationNames.ConstructorBinding] + = new FactoryMethodConstructorBinding( + TestProxyFactory.Instance, + typeof(TestProxyFactory).GetTypeInfo().GetDeclaredMethod(nameof(TestProxyFactory.Create)), + new List + { + new EntityTypeParameterBinding() + }); + + var factory = GetMaterializer(new EntityMaterializerSource(), entityType); + + var gu = Guid.NewGuid(); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), null); + + Assert.Equal(77, entity.Id); + Assert.Equal("Fu", entity.Foo); + Assert.Equal(gu, entity.Goo); + Assert.Equal(SomeEnum.EnumValue, entity.Enum); + Assert.Equal(SomeEnum.EnumValue, entity.MaybeEnum); + + Assert.False(entity.FactoryUsed); + Assert.False(entity.ParameterizedConstructorUsed); + Assert.True(entity.IdSetterCalled); + Assert.True(entity.GooSetterCalled); + } + + private class TestProxyFactory + { + public static readonly TestProxyFactory Instance = new TestProxyFactory(); + + public object Create(IEntityType entityType) + => Activator.CreateInstance(entityType.ClrType); + } + + private static EntityType CreateEntityType() { var entityType = new Model().AddEntityType(typeof(SomeEntity)); entityType.AddProperty(SomeEntity.EnumProperty); @@ -59,11 +129,18 @@ public void Can_create_materializer_for_entity_with_auto_properties() entityType.AddProperty(SomeEntity.GooProperty); entityType.AddProperty(SomeEntity.IdProperty); entityType.AddProperty(SomeEntity.MaybeEnumProperty); + return entityType; + } + + [Fact] + public void Can_create_materializer_for_entity_with_auto_properties() + { + var entityType = CreateEntityType(); var factory = GetMaterializer(new EntityMaterializerSource(), entityType); var gu = Guid.NewGuid(); - var entity = (SomeEntity)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue })); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), null); Assert.Equal(77, entity.Id); Assert.Equal("Fu", entity.Foo); @@ -85,7 +162,7 @@ public void Can_create_materializer_for_entity_with_fields() var factory = GetMaterializer(new EntityMaterializerSource(), entityType); var gu = Guid.NewGuid(); - var entity = (SomeEntityWithFields)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, null })); + var entity = (SomeEntityWithFields)factory(new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, null }), null); Assert.Equal(77, entity.Id); Assert.Equal("Fu", entity.Foo); @@ -104,7 +181,7 @@ public void Can_read_nulls() var factory = GetMaterializer(new EntityMaterializerSource(), entityType); - var entity = (SomeEntity)factory(new ValueBuffer(new object[] { null, null, 77 })); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { null, null, 77 }), null); Assert.Equal(77, entity.Id); Assert.Null(entity.Foo); @@ -125,7 +202,7 @@ public void Can_create_materializer_for_entity_ignoring_shadow_fields() var factory = GetMaterializer(new EntityMaterializerSource(), entityType); var gu = Guid.NewGuid(); - var entity = (SomeEntity)factory(new ValueBuffer(new object[] { "Fu", "FuS", gu, Guid.NewGuid(), 77, 777 })); + var entity = (SomeEntity)factory(new ValueBuffer(new object[] { "Fu", "FuS", gu, Guid.NewGuid(), 77, 777 }), null); Assert.Equal(77, entity.Id); Assert.Equal("Fu", entity.Foo); @@ -146,13 +223,41 @@ public void Throws_if_parameterless_constructor_is_not_defined_on_entity_type() private static readonly ParameterExpression _readerParameter = Expression.Parameter(typeof(ValueBuffer), "valueBuffer"); - public virtual Func GetMaterializer(IEntityMaterializerSource source, IEntityType entityType) => Expression.Lambda>( - source.CreateMaterializeExpression(entityType, _readerParameter), - _readerParameter) - .Compile(); + private static readonly ParameterExpression _contextParameter + = Expression.Parameter(typeof(DbContext), "context"); + + public virtual Func GetMaterializer(IEntityMaterializerSource source, IEntityType entityType) + => Expression.Lambda>( + source.CreateMaterializeExpression(entityType, _readerParameter, _contextParameter), + _readerParameter, + _contextParameter) + .Compile(); private class SomeEntity { + private int _hiddenId; + private Guid? _hiddenGoo; + + public SomeEntity() + { + } + + public SomeEntity(int id, Guid? goo) + { + _hiddenId = id; + _hiddenGoo = goo; + + ParameterizedConstructorUsed = true; + } + + public static SomeEntity Factory(int id, Guid? goo) + => new SomeEntity(id, goo) { FactoryUsed = true }; + + public bool FactoryUsed { get; set; } + public bool ParameterizedConstructorUsed { get; set; } + public bool IdSetterCalled { get; set; } + public bool GooSetterCalled { get; set; } + public static readonly PropertyInfo IdProperty = typeof(SomeEntity).GetProperty("Id"); public static readonly PropertyInfo FooProperty = typeof(SomeEntity).GetProperty("Foo"); public static readonly PropertyInfo GooProperty = typeof(SomeEntity).GetProperty("Goo"); @@ -160,10 +265,28 @@ private class SomeEntity public static readonly PropertyInfo MaybeEnumProperty = typeof(SomeEntity).GetProperty("MaybeEnum"); // ReSharper disable UnusedAutoPropertyAccessor.Local - public int Id { get; set; } + public int Id + { + get => _hiddenId; + set + { + IdSetterCalled = true; + _hiddenId = value; + } + } public string Foo { get; set; } - public Guid? Goo { get; set; } + + public Guid? Goo + { + get => _hiddenGoo; + set + { + GooSetterCalled = true; + _hiddenGoo = value; + } + } + public SomeEnum Enum { get; set; } public SomeEnum? MaybeEnum { get; set; } @@ -186,30 +309,11 @@ private class SomeEntityWithFields private SomeEnum? _maybeEnum; #pragma warning restore 649 - public int Id - { - get { return _id; } - } - - public string Foo - { - get { return _foo; } - } - - public Guid? Goo - { - get { return _goo; } - } - - public SomeEnum Enum - { - get { return _enum; } - } - - public SomeEnum? MaybeEnum - { - get { return _maybeEnum; } - } + public int Id => _id; + public string Foo => _foo; + public Guid? Goo => _goo; + public SomeEnum Enum => _enum; + public SomeEnum? MaybeEnum => _maybeEnum; } private enum SomeEnum diff --git a/test/EFCore.Tests/ModelSourceTest.cs b/test/EFCore.Tests/ModelSourceTest.cs index 6d6402a54b0..ab5e6918b4f 100644 --- a/test/EFCore.Tests/ModelSourceTest.cs +++ b/test/EFCore.Tests/ModelSourceTest.cs @@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -106,13 +105,9 @@ private class ConcreteModelSource : ModelSource public ConcreteModelSource(IDbSetFinder setFinder) : base( new ModelSourceDependencies( - new CoreConventionSetBuilder( - new CoreConventionSetBuilderDependencies( - TestServiceFactory.Instance.Create())), - new ModelCustomizer( - new ModelCustomizerDependencies(setFinder)), - new ModelCacheKeyFactory( - new ModelCacheKeyFactoryDependencies()))) + TestServiceFactory.Instance.Create(), + new ModelCustomizer(new ModelCustomizerDependencies(setFinder)), + new ModelCacheKeyFactory(new ModelCacheKeyFactoryDependencies()))) { } }