-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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<LazyPost> _lazyPosts = new List<LazyPost>(); public LazyBlog() { } private LazyBlog(ILazyLoader loader) { _loader = loader; } public int Id { get; set; } public ICollection<LazyPost> 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<object, string> _loader; private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>(); public LazyPocoBlog() { } private LazyPocoBlog(Action<object, string> lazyLoader) { _loader = lazyLoader; } public int Id { get; set; } public ICollection<LazyPocoPost> LazyPocoPosts => _loader.Load(this, ref _lazyPocoPosts); } public class LazyPocoPost { private readonly Action<object, string> _loader; private LazyPocoBlog _lazyPocoBlog; public LazyPocoPost() { } private LazyPocoPost(Action<object, string> 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<TRelated>( this Action<object, string> loader, object entity, ref TRelated navigationField, [CallerMemberName] string navigationName = null) where TRelated : class { loader?.Invoke(entity, navigationName); return navigationField; } } ```
- Loading branch information
1 parent
d4bc77c
commit 47d066d
Showing
101 changed files
with
5,347 additions
and
757 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
samples/OracleProvider/test/OracleProvider.FunctionalTests/WithConstructorsOracleTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<WithConstructorsOracleTest.WithConstructorsOracleFixture> | ||
{ | ||
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<HasContext<DbContext>>().ToTable("HasContext_DbContext"); | ||
modelBuilder.Entity<HasContext<WithConstructorsContext>>().ToTable("HasContext_WithConstructorsContext"); | ||
modelBuilder.Entity<HasContext<OtherContext>>().ToTable("HasContext_OtherContext"); | ||
|
||
modelBuilder.Entity<Blog>( | ||
b => { b.Property("_blogId").HasColumnName("BlogId"); }); | ||
|
||
modelBuilder.Entity<Post>( | ||
b => { b.Property("_id").HasColumnName("Id"); }); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.