Skip to content

Commit f794cd6

Browse files
authored
Merge pull request #14 from darxis/dev
Release 1.1.1-beta3
2 parents cb760e6 + 8ac7d0e commit f794cd6

25 files changed

+418
-196
lines changed

.travis.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
language: csharp
2+
solution: EntityFramework.LazyLoading.sln
3+
mono: none
4+
dotnet: 1.0.1
5+
dist: trusty
6+
sudo: required
7+
services:
8+
- mysql
9+
- postgresql
10+
env:
11+
global:
12+
- DB_NAME=ContosoUniversity
13+
matrix:
14+
- DB=PostgreSql CONFIGURATION=Release
15+
#- DB=MySql CONFIGURATION=Release
16+
install:
17+
- dotnet restore
18+
before_script:
19+
- export Microsoft_EntityFrameworkCore_LazyLoading_Tests_DatabaseType=$DB
20+
#- sh -c "if [ '$DB' = 'MySql' ]; then mysql -e 'CREATE DATABASE \`$DB_NAME\`; USE \`$DB_NAME\`; CREATE TABLE \`__EFMigrationsHistory\` (\`MigrationId\` nvarchar(150) NOT NULL, \`ProductVersion\` nvarchar(32) NOT NULL, PRIMARY KEY (\`MigrationId\`));'; (cd tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests && dotnet ef database update); fi"
21+
script:
22+
- dotnet build --configuration $CONFIGURATION
23+
- (cd tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests && dotnet test --configuration $CONFIGURATION)

README.md

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
# EntityFramework.LazyLoading
1+
# EntityFramework.LazyLoading [![Build Status](https://travis-ci.org/darxis/EntityFramework.LazyLoading.svg?branch=dev)](https://travis-ci.org/darxis/EntityFramework.LazyLoading)
22
Lazy Loading for EF Core
33

44
Inspired by and partially based on the blog post: https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core-part-6-lazy-loading
55

66
# How to enable LazyLoading in EF Core?
77

8-
1. Reference the `Microsoft.EntityFrameworkCore.LazyLoading` NuGet package (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/).
9-
2. Create (or modify an existing) DbContext factory. Include the lines inside the two `if(_isLazy)` blocks in your DbContext factory (3 lines total - 2 before building the DbContext, and 1 after):
8+
Enabling LazyLoading in EF Core is extremely easy with this library. You just need to call `UseLazyLoading()` (see step 2 below).
9+
10+
However, you will need to slightly modify your entity classes, but just the References, not the Collections (see step 3 below).
11+
12+
## Step 1 - Nuget package
13+
Reference the `Microsoft.EntityFrameworkCore.LazyLoading` NuGet package (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/).
14+
## Step 2 - Configure the DbContext builder
15+
Call `UseLazyLoading()` on the `DbContextOptionsBuilder` when creating the `DbContext`.
1016
```c#
1117
public class MyDbContextFactory : IDbContextFactory<MyDbContext>
1218
{
@@ -22,30 +28,15 @@ public class MyDbContextFactory : IDbContextFactory<MyDbContext>
2228
var dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
2329
dbContextOptionsBuilder.UseSqlServer("<some_connection_string>");
2430

25-
// LazyLoading specific
26-
if (_isLazy)
27-
{
28-
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Metadata.Internal.IEntityMaterializerSource, Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal.LazyLoadingEntityMaterializerSource<MyDbContext>>();
29-
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Internal.IConcurrencyDetector, Microsoft.EntityFrameworkCore.LazyLoading.Internal.ConcurrencyDetector>();
30-
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Query.Internal.ICompiledQueryCache, Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal.PerDbContextCompiledQueryCache>();
31-
}
32-
33-
34-
// Build DbContext
35-
var ctx = new MyDbContext(dbContextOptionsBuilder.Options);
36-
37-
// LazyLoading specific
38-
if (_isLazy)
39-
{
40-
(ctx.GetService<Microsoft.EntityFrameworkCore.Metadata.Internal.IEntityMaterializerSource>() as Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal.LazyLoadingEntityMaterializerSource<MyDbContext>).SetDbContext(ctx);
41-
(ctx.GetService<Microsoft.EntityFrameworkCore.Query.Internal.ICompiledQueryCache>() as Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal.PerDbContextCompiledQueryCache).SetDbContext(ctx);
42-
}
31+
// Here we enable LazyLoading
32+
dbContextOptionsBuilder.UseLazyLoading();
4333

44-
return ctx;
34+
return new MyDbContext(dbContextOptionsBuilder.Options);
4535
}
4636
}
4737
```
48-
3. In your model you need to declare References using the type LazyReference<T>. Collections don't require additional configuration in your model, just use the ICollection<> type.
38+
## Step 3 - Adjust the model
39+
In your model you need to declare References using the type `LazyReference<T>`. Collections don't require additional configuration in your model, just use the `ICollection<>` type.
4940
```c#
5041
public class Parent
5142
{
@@ -68,4 +59,5 @@ public class Child
6859
}
6960
}
7061
```
71-
4. That's all, LazyLoading enabled.
62+
## Step 4 - Done!
63+
That's all, LazyLoading enabled! It was so easy, wasn't it?
Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
using Microsoft.EntityFrameworkCore.Infrastructure;
2-
using Microsoft.EntityFrameworkCore.Metadata.Internal;
3-
using Microsoft.EntityFrameworkCore.LazyLoading.Internal;
4-
using Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal;
5-
using Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal;
6-
using Microsoft.EntityFrameworkCore.Query.Internal;
72

83
namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data.Factory
94
{
@@ -23,28 +18,17 @@ public SchoolContextFactory(bool isLazy)
2318
public SchoolContext Create(DbContextFactoryOptions options)
2419
{
2520
var dbContextOptionsBuilder = new DbContextOptionsBuilder<SchoolContext>();
21+
2622
dbContextOptionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity;Trusted_Connection=True;MultipleActiveResultSets=true");
2723

2824
// LazyLoading specific
2925
if (_isLazy)
3026
{
31-
dbContextOptionsBuilder.ReplaceService<IEntityMaterializerSource, LazyLoadingEntityMaterializerSource<SchoolContext>>();
32-
dbContextOptionsBuilder.ReplaceService<EntityFrameworkCore.Internal.IConcurrencyDetector, ConcurrencyDetector>();
33-
dbContextOptionsBuilder.ReplaceService<ICompiledQueryCache, PerDbContextCompiledQueryCache>();
27+
dbContextOptionsBuilder.UseLazyLoading();
3428
}
35-
3629

3730
// Build DbContext
38-
var ctx = new SchoolContext(dbContextOptionsBuilder.Options);
39-
40-
// LazyLoading specific
41-
if (_isLazy)
42-
{
43-
(ctx.GetService<IEntityMaterializerSource>() as LazyLoadingEntityMaterializerSource<SchoolContext>).SetDbContext(ctx);
44-
(ctx.GetService<ICompiledQueryCache>() as PerDbContextCompiledQueryCache).SetDbContext(ctx);
45-
}
46-
47-
return ctx;
31+
return new SchoolContext(dbContextOptionsBuilder.Options);
4832
}
4933
}
5034
}

samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Data/SchoolContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
2626
modelBuilder.Entity<Instructor>();
2727
modelBuilder.Entity<OfficeAssignment>();
2828
modelBuilder.Entity<CourseAssignment>();
29-
modelBuilder.Entity<Person>();
29+
modelBuilder.Entity<Person>()
30+
.HasDiscriminator<string>("Discriminator")
31+
.HasValue<Student>(nameof(Student))
32+
.HasValue<Instructor>(nameof(Instructor));
3033

3134
modelBuilder.Entity<CourseAssignment>()
3235
.HasKey(c => new { c.CourseID, c.InstructorID });

samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample
99
{
1010
class Program
1111
{
12+
// ReSharper disable once UnusedMember.Local
13+
// ReSharper disable once UnusedParameter.Local
1214
static void Main(string[] args)
1315
{
1416
var lazyFactory = new SchoolContextFactory(true);
@@ -47,8 +49,9 @@ static void Main(string[] args)
4749
var student = dbContext.Students.First();
4850
try
4951
{
52+
// ReSharper disable once UnusedVariable
5053
var deptName = student.Enrollments.First().Course.Department.Name;
51-
Console.WriteLine($"Oops... Something didn't work. LazyLoading should not be enabled by default.");
54+
Console.WriteLine("Oops... Something didn't work. LazyLoading should not be enabled by default.");
5255
}
5356
catch (ArgumentNullException)
5457
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
using Microsoft.EntityFrameworkCore.LazyLoading.Internal;
3+
using Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal;
4+
using Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal;
5+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
6+
using Microsoft.EntityFrameworkCore.Query.Internal;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal
10+
{
11+
public class LazyLoadingOptionsExtension : IDbContextOptionsExtension
12+
{
13+
public LazyLoadingOptionsExtension()
14+
{
15+
}
16+
17+
// NB: When adding new options, make sure to update the copy ctor below.
18+
19+
// ReSharper disable once UnusedParameter.Local
20+
public LazyLoadingOptionsExtension(LazyLoadingOptionsExtension copyFrom)
21+
{
22+
}
23+
24+
public void ApplyServices(IServiceCollection services)
25+
{
26+
services.AddScoped<IEntityMaterializerSource, LazyLoadingEntityMaterializerSource>();
27+
services.AddScoped<EntityFrameworkCore.Internal.IConcurrencyDetector, ConcurrencyDetector>();
28+
services.AddScoped<ICompiledQueryCache, PerDbContextCompiledQueryCache>();
29+
}
30+
}
31+
}

src/Microsoft.EntityFrameworkCore.LazyLoading/LazyCollection.cs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class LazyCollection<T> : IList<T>
1515
private readonly DbContext _ctx;
1616
private readonly string _collectionName;
1717
private readonly object _parent;
18-
private readonly IList<T> _entries = new List<T>();
18+
private IList<T> _entries = new List<T>();
1919

2020
public LazyCollection(DbContext ctx, object parent, string collectionName)
2121
{
@@ -26,48 +26,41 @@ public LazyCollection(DbContext ctx, object parent, string collectionName)
2626

2727
private void EnsureLoaded()
2828
{
29-
if (!_loaded && !_loading)
29+
if (_loaded || _loading)
3030
{
31-
_loading = true;
31+
return;
32+
}
33+
34+
_loading = true;
3235

33-
var concurrencyDetector = _ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
36+
try
37+
{
38+
var concurrencyDetector =
39+
_ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
3440
if (concurrencyDetector == null)
3541
{
36-
_loading = false;
37-
throw new LazyLoadingConfigurationException($"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading");
42+
throw new LazyLoadingConfigurationException(
43+
$"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading");
3844
}
3945

4046
if (concurrencyDetector.IsInOperation())
4147
{
42-
_loading = false;
4348
return;
4449
}
4550

46-
var entries = _ctx
51+
_entries = _ctx
4752
.Entry(_parent)
4853
.Collection(_collectionName)
4954
.Query()
5055
.OfType<T>()
5156
.ToList();
52-
53-
/*if (typeof(ILazyLoading).IsAssignableFrom(typeof(T)))
54-
{
55-
foreach (var entry in entries)
56-
{
57-
((ILazyLoading)entry).UseLazyLoading(_ctx);
58-
}
59-
}*/
60-
61-
_entries.Clear();
62-
63-
foreach (var entry in entries)
64-
{
65-
_entries.Add(entry);
66-
}
67-
68-
_loaded = true;
57+
}
58+
finally
59+
{
6960
_loading = false;
7061
}
62+
63+
_loaded = true;
7164
}
7265

7366
IEnumerator<T> IEnumerable<T>.GetEnumerator()
@@ -161,11 +154,5 @@ public override string ToString()
161154
EnsureLoaded();
162155
return _entries.ToString();
163156
}
164-
165-
public override int GetHashCode()
166-
{
167-
EnsureLoaded();
168-
return _entries.GetHashCode();
169-
}
170157
}
171158
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
using Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal;
3+
4+
// ReSharper disable once CheckNamespace
5+
namespace Microsoft.EntityFrameworkCore
6+
{
7+
public static class LazyLoadingDbContextOptionsBuilderExtensions
8+
{
9+
public static DbContextOptionsBuilder UseLazyLoading(this DbContextOptionsBuilder optionsBuilder)
10+
{
11+
var extension = GetOrCreateExtension(optionsBuilder);
12+
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(extension);
13+
14+
return optionsBuilder;
15+
}
16+
17+
public static DbContextOptionsBuilder<TContext> UseLazyLoading<TContext>(
18+
this DbContextOptionsBuilder<TContext> optionsBuilder)
19+
where TContext : DbContext
20+
=> (DbContextOptionsBuilder<TContext>) UseLazyLoading((DbContextOptionsBuilder) optionsBuilder);
21+
22+
private static LazyLoadingOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
23+
{
24+
var existing = optionsBuilder.Options.FindExtension<LazyLoadingOptionsExtension>();
25+
return existing != null
26+
? new LazyLoadingOptionsExtension(existing)
27+
: new LazyLoadingOptionsExtension();
28+
}
29+
}
30+
}

src/Microsoft.EntityFrameworkCore.LazyLoading/LazyReference.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,32 @@ public void SetValue(T value)
2828

2929
public T GetValue(object parent, string referenceName)
3030
{
31-
if (_ctx != null && !_isLoaded && !_isLoading)
31+
if (_ctx == null || _isLoaded || _isLoading)
3232
{
33-
_isLoading = true;
33+
return _value;
34+
}
35+
36+
_isLoading = true;
3437

35-
var concurrencyDetector = _ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
38+
try
39+
{
40+
var concurrencyDetector =
41+
_ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
3642
if (concurrencyDetector == null)
3743
{
38-
_isLoading = false;
39-
throw new LazyLoadingConfigurationException($"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading");
44+
throw new LazyLoadingConfigurationException(
45+
$"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading");
4046
}
4147

4248
if (concurrencyDetector.IsInOperation())
4349
{
44-
_isLoading = false;
4550
return _value;
4651
}
4752

4853
_ctx.Entry(parent).Reference(referenceName).Load();
49-
54+
}
55+
finally
56+
{
5057
_isLoading = false;
5158
}
5259

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
namespace Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal
22
{
3-
public interface ILazyLoadingEntityMaterializerSource<in TDbContext>
4-
where TDbContext : DbContext
3+
public interface ILazyLoadingEntityMaterializerSource
54
{
6-
void SetDbContext(TDbContext ctx);
75
}
86
}

0 commit comments

Comments
 (0)