Skip to content

Commit

Permalink
Merge pull request #14 from darxis/dev
Browse files Browse the repository at this point in the history
Release 1.1.1-beta3
  • Loading branch information
darxis authored Apr 7, 2017
2 parents cb760e6 + 8ac7d0e commit f794cd6
Show file tree
Hide file tree
Showing 25 changed files with 418 additions and 196 deletions.
23 changes: 23 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
language: csharp
solution: EntityFramework.LazyLoading.sln
mono: none
dotnet: 1.0.1
dist: trusty
sudo: required
services:
- mysql
- postgresql
env:
global:
- DB_NAME=ContosoUniversity
matrix:
- DB=PostgreSql CONFIGURATION=Release
#- DB=MySql CONFIGURATION=Release
install:
- dotnet restore
before_script:
- export Microsoft_EntityFrameworkCore_LazyLoading_Tests_DatabaseType=$DB
#- 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"
script:
- dotnet build --configuration $CONFIGURATION
- (cd tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests && dotnet test --configuration $CONFIGURATION)
40 changes: 16 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# EntityFramework.LazyLoading
# EntityFramework.LazyLoading [![Build Status](https://travis-ci.org/darxis/EntityFramework.LazyLoading.svg?branch=dev)](https://travis-ci.org/darxis/EntityFramework.LazyLoading)
Lazy Loading for EF Core

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

# How to enable LazyLoading in EF Core?

1. Reference the `Microsoft.EntityFrameworkCore.LazyLoading` NuGet package (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/).
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):
Enabling LazyLoading in EF Core is extremely easy with this library. You just need to call `UseLazyLoading()` (see step 2 below).

However, you will need to slightly modify your entity classes, but just the References, not the Collections (see step 3 below).

## Step 1 - Nuget package
Reference the `Microsoft.EntityFrameworkCore.LazyLoading` NuGet package (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/).
## Step 2 - Configure the DbContext builder
Call `UseLazyLoading()` on the `DbContextOptionsBuilder` when creating the `DbContext`.
```c#
public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
Expand All @@ -22,30 +28,15 @@ public class MyDbContextFactory : IDbContextFactory<MyDbContext>
var dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
dbContextOptionsBuilder.UseSqlServer("<some_connection_string>");

// LazyLoading specific
if (_isLazy)
{
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Metadata.Internal.IEntityMaterializerSource, Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal.LazyLoadingEntityMaterializerSource<MyDbContext>>();
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Internal.IConcurrencyDetector, Microsoft.EntityFrameworkCore.LazyLoading.Internal.ConcurrencyDetector>();
dbContextOptionsBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Query.Internal.ICompiledQueryCache, Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal.PerDbContextCompiledQueryCache>();
}


// Build DbContext
var ctx = new MyDbContext(dbContextOptionsBuilder.Options);

// LazyLoading specific
if (_isLazy)
{
(ctx.GetService<Microsoft.EntityFrameworkCore.Metadata.Internal.IEntityMaterializerSource>() as Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal.LazyLoadingEntityMaterializerSource<MyDbContext>).SetDbContext(ctx);
(ctx.GetService<Microsoft.EntityFrameworkCore.Query.Internal.ICompiledQueryCache>() as Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal.PerDbContextCompiledQueryCache).SetDbContext(ctx);
}
// Here we enable LazyLoading
dbContextOptionsBuilder.UseLazyLoading();

return ctx;
return new MyDbContext(dbContextOptionsBuilder.Options);
}
}
```
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.
## Step 3 - Adjust the model
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.
```c#
public class Parent
{
Expand All @@ -68,4 +59,5 @@ public class Child
}
}
```
4. That's all, LazyLoading enabled.
## Step 4 - Done!
That's all, LazyLoading enabled! It was so easy, wasn't it?
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.LazyLoading.Internal;
using Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal;
using Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data.Factory
{
Expand All @@ -23,28 +18,17 @@ public SchoolContextFactory(bool isLazy)
public SchoolContext Create(DbContextFactoryOptions options)
{
var dbContextOptionsBuilder = new DbContextOptionsBuilder<SchoolContext>();

dbContextOptionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity;Trusted_Connection=True;MultipleActiveResultSets=true");

// LazyLoading specific
if (_isLazy)
{
dbContextOptionsBuilder.ReplaceService<IEntityMaterializerSource, LazyLoadingEntityMaterializerSource<SchoolContext>>();
dbContextOptionsBuilder.ReplaceService<EntityFrameworkCore.Internal.IConcurrencyDetector, ConcurrencyDetector>();
dbContextOptionsBuilder.ReplaceService<ICompiledQueryCache, PerDbContextCompiledQueryCache>();
dbContextOptionsBuilder.UseLazyLoading();
}


// Build DbContext
var ctx = new SchoolContext(dbContextOptionsBuilder.Options);

// LazyLoading specific
if (_isLazy)
{
(ctx.GetService<IEntityMaterializerSource>() as LazyLoadingEntityMaterializerSource<SchoolContext>).SetDbContext(ctx);
(ctx.GetService<ICompiledQueryCache>() as PerDbContextCompiledQueryCache).SetDbContext(ctx);
}

return ctx;
return new SchoolContext(dbContextOptionsBuilder.Options);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Instructor>();
modelBuilder.Entity<OfficeAssignment>();
modelBuilder.Entity<CourseAssignment>();
modelBuilder.Entity<Person>();
modelBuilder.Entity<Person>()
.HasDiscriminator<string>("Discriminator")
.HasValue<Student>(nameof(Student))
.HasValue<Instructor>(nameof(Instructor));

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample
{
class Program
{
// ReSharper disable once UnusedMember.Local
// ReSharper disable once UnusedParameter.Local
static void Main(string[] args)
{
var lazyFactory = new SchoolContextFactory(true);
Expand Down Expand Up @@ -47,8 +49,9 @@ static void Main(string[] args)
var student = dbContext.Students.First();
try
{
// ReSharper disable once UnusedVariable
var deptName = student.Enrollments.First().Course.Department.Name;
Console.WriteLine($"Oops... Something didn't work. LazyLoading should not be enabled by default.");
Console.WriteLine("Oops... Something didn't work. LazyLoading should not be enabled by default.");
}
catch (ArgumentNullException)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.LazyLoading.Internal;
using Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal;
using Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal
{
public class LazyLoadingOptionsExtension : IDbContextOptionsExtension
{
public LazyLoadingOptionsExtension()
{
}

// NB: When adding new options, make sure to update the copy ctor below.

// ReSharper disable once UnusedParameter.Local
public LazyLoadingOptionsExtension(LazyLoadingOptionsExtension copyFrom)
{
}

public void ApplyServices(IServiceCollection services)
{
services.AddScoped<IEntityMaterializerSource, LazyLoadingEntityMaterializerSource>();
services.AddScoped<EntityFrameworkCore.Internal.IConcurrencyDetector, ConcurrencyDetector>();
services.AddScoped<ICompiledQueryCache, PerDbContextCompiledQueryCache>();
}
}
}
49 changes: 18 additions & 31 deletions src/Microsoft.EntityFrameworkCore.LazyLoading/LazyCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class LazyCollection<T> : IList<T>
private readonly DbContext _ctx;
private readonly string _collectionName;
private readonly object _parent;
private readonly IList<T> _entries = new List<T>();
private IList<T> _entries = new List<T>();

public LazyCollection(DbContext ctx, object parent, string collectionName)
{
Expand All @@ -26,48 +26,41 @@ public LazyCollection(DbContext ctx, object parent, string collectionName)

private void EnsureLoaded()
{
if (!_loaded && !_loading)
if (_loaded || _loading)
{
_loading = true;
return;
}

_loading = true;

var concurrencyDetector = _ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
try
{
var concurrencyDetector =
_ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
if (concurrencyDetector == null)
{
_loading = false;
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");
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");
}

if (concurrencyDetector.IsInOperation())
{
_loading = false;
return;
}

var entries = _ctx
_entries = _ctx
.Entry(_parent)
.Collection(_collectionName)
.Query()
.OfType<T>()
.ToList();

/*if (typeof(ILazyLoading).IsAssignableFrom(typeof(T)))
{
foreach (var entry in entries)
{
((ILazyLoading)entry).UseLazyLoading(_ctx);
}
}*/

_entries.Clear();

foreach (var entry in entries)
{
_entries.Add(entry);
}

_loaded = true;
}
finally
{
_loading = false;
}

_loaded = true;
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
Expand Down Expand Up @@ -161,11 +154,5 @@ public override string ToString()
EnsureLoaded();
return _entries.ToString();
}

public override int GetHashCode()
{
EnsureLoaded();
return _entries.GetHashCode();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
public static class LazyLoadingDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder UseLazyLoading(this DbContextOptionsBuilder optionsBuilder)
{
var extension = GetOrCreateExtension(optionsBuilder);
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(extension);

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseLazyLoading<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>) UseLazyLoading((DbContextOptionsBuilder) optionsBuilder);

private static LazyLoadingOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
{
var existing = optionsBuilder.Options.FindExtension<LazyLoadingOptionsExtension>();
return existing != null
? new LazyLoadingOptionsExtension(existing)
: new LazyLoadingOptionsExtension();
}
}
}
21 changes: 14 additions & 7 deletions src/Microsoft.EntityFrameworkCore.LazyLoading/LazyReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,32 @@ public void SetValue(T value)

public T GetValue(object parent, string referenceName)
{
if (_ctx != null && !_isLoaded && !_isLoading)
if (_ctx == null || _isLoaded || _isLoading)
{
_isLoading = true;
return _value;
}

_isLoading = true;

var concurrencyDetector = _ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
try
{
var concurrencyDetector =
_ctx.GetService<EntityFrameworkCore.Internal.IConcurrencyDetector>() as IConcurrencyDetector;
if (concurrencyDetector == null)
{
_isLoading = false;
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");
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");
}

if (concurrencyDetector.IsInOperation())
{
_isLoading = false;
return _value;
}

_ctx.Entry(parent).Reference(referenceName).Load();

}
finally
{
_isLoading = false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
namespace Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal
{
public interface ILazyLoadingEntityMaterializerSource<in TDbContext>
where TDbContext : DbContext
public interface ILazyLoadingEntityMaterializerSource
{
void SetDbContext(TDbContext ctx);
}
}
Loading

0 comments on commit f794cd6

Please sign in to comment.