Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of lazy-loading and entities with constructors #10624

Merged
merged 1 commit into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
}
Expand Down
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"); });
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,8 @@ protected virtual void GenerateEntityTypeAnnotations(
annotations,
RelationshipDiscoveryConvention.NavigationCandidatesAnnotationName,
RelationshipDiscoveryConvention.AmbiguousNavigationsAnnotationName,
InversePropertyAttributeConvention.InverseNavigationsAnnotationName);
InversePropertyAttributeConvention.InverseNavigationsAnnotationName,
CoreAnnotationNames.ConstructorBinding);

if (annotations.Any())
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.InMemory/Query/Internal/IMaterializerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
Expression<Func<IEntityType, ValueBuffer, object>> CreateMaterializer([NotNull] IEntityType entityType);
Expression<Func<IEntityType, ValueBuffer, DbContext, object>> CreateMaterializer([NotNull] IEntityType entityType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajcvickers
It would be slightly more query-idiomatic to take QueryContext here. We could also introduce a MaterializationContext type here that so that the delegate would be Func<MaterializationContext, object>. The advantage would be to simplify further changes and to DRY things up a bit - Changes here seem pretty viral and unpleasant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with @anpete and created issue #10641

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static IEnumerable<TEntity> EntityQuery<TEntity>(
QueryContext queryContext,
IEntityType entityType,
IKey key,
Func<IEntityType, ValueBuffer, object> materializer,
Func<IEntityType, ValueBuffer, DbContext, object> materializer,
bool queryStateManager)
where TEntity : class
=> ((InMemoryQueryContext)queryContext).Store
Expand All @@ -67,7 +67,8 @@ private static IEnumerable<TEntity> EntityQuery<TEntity>(
key,
new EntityLoadInfo(
valueBuffer,
vr => materializer(t.EntityType, vr)),
queryContext.Context,
(vr, c) => materializer(t.EntityType, vr, c)),
queryStateManager,
throwOnNullKey: false);
}));
Expand Down
21 changes: 13 additions & 8 deletions src/EFCore.InMemory/Query/Internal/MaterializerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public virtual Expression<Func<IEntityType, ValueBuffer, object>> CreateMaterializer(IEntityType entityType)
public virtual Expression<Func<IEntityType, ValueBuffer, DbContext, object>> CreateMaterializer(IEntityType entityType)
{
Check.NotNull(entityType, nameof(entityType));

Expand All @@ -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<Func<IEntityType, ValueBuffer, object>>(
return Expression.Lambda<Func<IEntityType, ValueBuffer, DbContext, object>>(
_entityMaterializerSource
.CreateMaterializeExpression(
concreteEntityTypes[0], valueBufferParameter),
concreteEntityTypes[0], valueBufferParameter, contextParameter),
entityTypeParameter,
valueBufferParameter);
valueBufferParameter,
contextParameter);
}

var returnLabelTarget = Expression.Label(typeof(object));
Expand All @@ -71,7 +75,7 @@ var blockExpressions
returnLabelTarget,
_entityMaterializerSource
.CreateMaterializeExpression(
concreteEntityTypes[0], valueBufferParameter))),
concreteEntityTypes[0], valueBufferParameter, contextParameter))),
Expression.Label(
returnLabelTarget,
Expression.Default(returnLabelTarget.Type))
Expand All @@ -87,14 +91,15 @@ var blockExpressions
Expression.Return(
returnLabelTarget,
_entityMaterializerSource
.CreateMaterializeExpression(concreteEntityType, valueBufferParameter)),
.CreateMaterializeExpression(concreteEntityType, valueBufferParameter, contextParameter)),
blockExpressions[0]);
}

return Expression.Lambda<Func<IEntityType, ValueBuffer, object>>(
return Expression.Lambda<Func<IEntityType, ValueBuffer, DbContext, object>>(
Expression.Block(blockExpressions),
entityTypeParameter,
valueBufferParameter);
valueBufferParameter,
contextParameter);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public BufferedEntityShaper(
[NotNull] IQuerySource querySource,
bool trackingQuery,
[NotNull] IKey key,
[NotNull] Func<ValueBuffer, object> materializer,
[NotNull] Func<ValueBuffer, DbContext, object> materializer,
[CanBeNull] Dictionary<Type, int[]> typeIndexMap)
: base(querySource, trackingQuery, key, materializer)
{
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public BufferedOffsetEntityShaper(
[NotNull] IQuerySource querySource,
bool trackingQuery,
[NotNull] IKey key,
[NotNull] Func<ValueBuffer, object> materializer,
[NotNull] Func<ValueBuffer, DbContext, object> materializer,
[CanBeNull] Dictionary<Type, int[]> typeIndexMap)
: base(querySource, trackingQuery, key, materializer, typeIndexMap)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected EntityShaper(
[NotNull] IQuerySource querySource,
bool trackingQuery,
[NotNull] IKey key,
[NotNull] Func<ValueBuffer, object> materializer)
[NotNull] Func<ValueBuffer, DbContext, object> materializer)
: base(querySource)
{
IsTrackingQuery = trackingQuery;
Expand All @@ -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.
/// </summary>
protected virtual Func<ValueBuffer, object> Materializer { get; }
protected virtual Func<ValueBuffer, DbContext, object> Materializer { get; }

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
Expression<Func<ValueBuffer, object>> CreateMaterializer(
Expression<Func<ValueBuffer, DbContext, object>> CreateMaterializer(
[NotNull] IEntityType entityType,
[NotNull] SelectExpression selectExpression,
[NotNull] Func<IProperty, SelectExpression, int> projectionAdder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public virtual Expression<Func<ValueBuffer, object>> CreateMaterializer(
public virtual Expression<Func<ValueBuffer, DbContext, object>> CreateMaterializer(
IEntityType entityType,
SelectExpression selectExpression,
Func<IProperty, SelectExpression, int> projectionAdder,
Expand All @@ -57,6 +57,9 @@ public virtual Expression<Func<ValueBuffer, object>> CreateMaterializer(
var valueBufferParameter
= Expression.Parameter(typeof(ValueBuffer), "valueBuffer");

var contextParameter
= Expression.Parameter(typeof(DbContext), "context");

var concreteEntityTypes
= entityType.GetConcreteTypesInHierarchy().ToList();

Expand All @@ -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<Func<ValueBuffer, object>>(materializer, valueBufferParameter);
return Expression.Lambda<Func<ValueBuffer, DbContext, object>>(
materializer, valueBufferParameter, contextParameter);
}

var discriminatorProperty = concreteEntityTypes[0].Relational().DiscriminatorProperty;
Expand All @@ -98,7 +102,8 @@ var discriminatorPredicate
selectExpression.Predicate
= new DiscriminatorPredicateExpression(discriminatorPredicate, querySource);

return Expression.Lambda<Func<ValueBuffer, object>>(materializer, valueBufferParameter);
return Expression.Lambda<Func<ValueBuffer, DbContext, object>>(
materializer, valueBufferParameter, contextParameter);
}

var discriminatorValueVariable
Expand Down Expand Up @@ -160,7 +165,8 @@ var discriminatorValue

materializer
= _entityMaterializerSource
.CreateMaterializeExpression(concreteEntityType, valueBufferParameter, indexMap);
.CreateMaterializeExpression(
concreteEntityType, valueBufferParameter, contextParameter, indexMap);

blockExpressions[1]
= Expression.IfThenElse(
Expand All @@ -177,9 +183,10 @@ var discriminatorValue
selectExpression.Predicate
= new DiscriminatorPredicateExpression(discriminatorPredicate, querySource);

return Expression.Lambda<Func<ValueBuffer, object>>(
return Expression.Lambda<Func<ValueBuffer, DbContext, object>>(
Expression.Block(new[] { discriminatorValueVariable }, blockExpressions),
valueBufferParameter);
valueBufferParameter,
contextParameter);
}

private static readonly MethodInfo _createUnableToDiscriminateException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public UnbufferedEntityShaper(
[NotNull] IQuerySource querySource,
bool trackingQuery,
[NotNull] IKey key,
[NotNull] Func<ValueBuffer, object> materializer)
[NotNull] Func<ValueBuffer, DbContext, object> materializer)
: base(querySource, trackingQuery, key, materializer)
{
}
Expand All @@ -51,7 +51,7 @@ public virtual TEntity Shape(QueryContext queryContext, ValueBuffer valueBuffer)
}
}

return (TEntity)Materializer(valueBuffer);
return (TEntity)Materializer(valueBuffer, queryContext.Context);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public UnbufferedOffsetEntityShaper(
[NotNull] IQuerySource querySource,
bool trackingQuery,
[NotNull] IKey key,
[NotNull] Func<ValueBuffer, object> materializer)
[NotNull] Func<ValueBuffer, DbContext, object> materializer)
: base(querySource, trackingQuery, key, materializer)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ private static IShaper<TEntity> CreateEntityShaper<TEntity>(
IQuerySource querySource,
bool trackingQuery,
IKey key,
Func<ValueBuffer, object> materializer,
Func<ValueBuffer, DbContext, object> materializer,
Dictionary<Type, int[]> typeIndexMap,
bool useQueryBuffer)
where TEntity : class
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore.Specification.Tests/DatabindingTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand All @@ -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;

Expand Down
Loading