Part of #10509, #3797.

This change adds a new package--Microsoft.EntityFrameworkCore.Proxies--that contains a lazy-loading proxy implementation making use of the EF lazy-loading infrastructure and the Castle.Core proxies package. Current plan is for this to ship on NuGet as an optional package for use with EF.

To use in a normal application, just add a call to `UseLazyLoadingProxies()` on the DbContext options builder.

There are also `context.CreateProxy()` methods for creating stand-alone proxy instances if needed.

Entity types must be public and navigation properties must be virtual. Also, the entity type and constructor must be "visible" to the castle proxies assembly, which usually means public, but could mean internal if "InternalsVisibleTo" is used. Exceptions are thrown if these requirements are not met--you don't just not get a proxy like in EF6.

Note that this is an optional package for EF that we chose to create because the infrastructure in place made it easy to do so. It does not preclude a Roslyn-based rewriting solution in the future.
This commit is contained in:
Arthur Vickers 2018-01-11 17:25:08 -08:00
Родитель 714e13180f
Коммит 972b71ffaf
54 изменённых файлов: 10099 добавлений и 102 удалений

Просмотреть файл

@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Design", "src\EFCore
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.InMemory", "src\EFCore.InMemory\EFCore.InMemory.csproj", "{6B102CC4-4396-4A7B-9F72-2C6B5C4D8310}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Proxies", "src\EFCore.Proxies\EFCore.Proxies.csproj", "{467616E6-30EA-4861-B96B-9ABD46BE41EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Relational", "src\EFCore.Relational\EFCore.Relational.csproj", "{6A25DF99-2615-46D8-9532-821764647EE1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Sqlite", "src\EFCore.Sqlite\EFCore.Sqlite.csproj", "{6FF0B963-0544-4428-8AE1-5A71872B55AD}"
@ -67,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.InMemory.FunctionalT
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.InMemory.Tests", "test\EFCore.InMemory.Tests\EFCore.InMemory.Tests.csproj", "{AB8D4BD7-8AB6-4004-AF21-32790EED93BC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Proxies.Tests", "test\EFCore.Proxies.Tests\EFCore.Proxies.Tests.csproj", "{007D03D8-D4A2-4398-A39C-F84A90027161}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Relational.Specification.Tests", "src\EFCore.Relational.Specification.Tests\EFCore.Relational.Specification.Tests.csproj", "{07FA2B15-A6A5-4292-A096-7771FB32EEDA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Relational.Tests", "test\EFCore.Relational.Tests\EFCore.Relational.Tests.csproj", "{1A884122-DC9E-42B1-8821-E43340F954D1}"
@ -119,6 +123,10 @@ Global
{6B102CC4-4396-4A7B-9F72-2C6B5C4D8310}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B102CC4-4396-4A7B-9F72-2C6B5C4D8310}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B102CC4-4396-4A7B-9F72-2C6B5C4D8310}.Release|Any CPU.Build.0 = Release|Any CPU
{467616E6-30EA-4861-B96B-9ABD46BE41EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{467616E6-30EA-4861-B96B-9ABD46BE41EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{467616E6-30EA-4861-B96B-9ABD46BE41EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{467616E6-30EA-4861-B96B-9ABD46BE41EA}.Release|Any CPU.Build.0 = Release|Any CPU
{6A25DF99-2615-46D8-9532-821764647EE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A25DF99-2615-46D8-9532-821764647EE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A25DF99-2615-46D8-9532-821764647EE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -167,6 +175,10 @@ Global
{AB8D4BD7-8AB6-4004-AF21-32790EED93BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB8D4BD7-8AB6-4004-AF21-32790EED93BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB8D4BD7-8AB6-4004-AF21-32790EED93BC}.Release|Any CPU.Build.0 = Release|Any CPU
{007D03D8-D4A2-4398-A39C-F84A90027161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{007D03D8-D4A2-4398-A39C-F84A90027161}.Debug|Any CPU.Build.0 = Debug|Any CPU
{007D03D8-D4A2-4398-A39C-F84A90027161}.Release|Any CPU.ActiveCfg = Release|Any CPU
{007D03D8-D4A2-4398-A39C-F84A90027161}.Release|Any CPU.Build.0 = Release|Any CPU
{07FA2B15-A6A5-4292-A096-7771FB32EEDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07FA2B15-A6A5-4292-A096-7771FB32EEDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07FA2B15-A6A5-4292-A096-7771FB32EEDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -218,6 +230,7 @@ Global
{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{D3D0A8E8-EC2F-4E01-8650-8554E186A66F} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{6B102CC4-4396-4A7B-9F72-2C6B5C4D8310} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{467616E6-30EA-4861-B96B-9ABD46BE41EA} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{6A25DF99-2615-46D8-9532-821764647EE1} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{6FF0B963-0544-4428-8AE1-5A71872B55AD} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{A257C01B-BB91-44BA-831C-1E04F7800AC8} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
@ -230,6 +243,7 @@ Global
{7583E3F0-8B29-4BEA-A55E-E8B66E6FD508} = {258D5057-81B9-40EC-A872-D21E27452749}
{305B30D3-0E30-46E9-BA9D-060E0B79BE98} = {258D5057-81B9-40EC-A872-D21E27452749}
{AB8D4BD7-8AB6-4004-AF21-32790EED93BC} = {258D5057-81B9-40EC-A872-D21E27452749}
{007D03D8-D4A2-4398-A39C-F84A90027161} = {258D5057-81B9-40EC-A872-D21E27452749}
{07FA2B15-A6A5-4292-A096-7771FB32EEDA} = {258D5057-81B9-40EC-A872-D21E27452749}
{1A884122-DC9E-42B1-8821-E43340F954D1} = {258D5057-81B9-40EC-A872-D21E27452749}
{1A73D95E-E8B5-4F96-908C-7B040E4F7AFE} = {258D5057-81B9-40EC-A872-D21E27452749}

Просмотреть файл

@ -31,6 +31,7 @@
<SystemDataSqlClientPackageVersion>4.5.0-preview1-26016-05</SystemDataSqlClientPackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.5.0-preview1-26016-05</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemInteractiveAsyncPackageVersion>3.1.1</SystemInteractiveAsyncPackageVersion>
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
<XunitAssertPackageVersion>2.3.1</XunitAssertPackageVersion>
<XunitCorePackageVersion>2.3.1</XunitCorePackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.3.1</XunitRunnerVisualStudioPackageVersion>

Просмотреть файл

@ -0,0 +1,29 @@
// 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;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore
{
public class LazyLoadProxyOracleTest : LazyLoadProxyTestBase<LazyLoadProxyOracleTest.LoadOracleFixture>
{
public LazyLoadProxyOracleTest(LoadOracleFixture fixture)
: base(fixture)
{
}
public class LoadOracleFixture : LoadFixtureBase
{
public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService<ILoggerFactory>();
protected override ITestStoreFactory TestStoreFactory => OracleTestStoreFactory.Instance;
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
c => c
.Log(RelationalEventId.QueryClientEvaluationWarning));
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Lazy-loading proxies for EF Core.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<MinClientVersion>3.6</MinClientVersion>
<AssemblyName>Microsoft.EntityFrameworkCore.Proxies</AssemblyName>
<RootNamespace>Microsoft.EntityFrameworkCore</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>$(PackageTags);Lazy-loading</PackageTags>
<CodeAnalysisRuleSet>..\EFCore.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Castle.Core" Version="$(CastleCorePackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EFCore\EFCore.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\ProxiesStrings.Designer.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ProxiesStrings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\ProxiesStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ProxiesStrings.Designer.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\ProxiesStrings.resx">
<CustomToolNamespace>Microsoft.EntityFrameworkCore.Internal</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopAnalyzersPackageVersion)" PrivateAssets="All" />
</ItemGroup>
</Project>

70
src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,70 @@
// <auto-generated />
using System;
using System.Reflection;
using System.Resources;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore.Internal
{
/// <summary>
/// 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 static class ProxiesStrings
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.EntityFrameworkCore.Properties.ProxiesStrings", typeof(ProxiesStrings).GetTypeInfo().Assembly);
/// <summary>
/// UseLazyLoadingProxies requires AddEntityFrameworkProxies to be called on the internal service provider used.
/// </summary>
public static string ProxyServicesMissing
=> GetString("ProxyServicesMissing");
/// <summary>
/// Entity type '{entityType}' is sealed. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.
/// </summary>
public static string ItsASeal([CanBeNull] object entityType)
=> string.Format(
GetString("ItsASeal", nameof(entityType)),
entityType);
/// <summary>
/// Navigation property '{navigation}' on entity type '{entityType}' is not virtual. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.
/// </summary>
public static string NonVirtualNavigation([CanBeNull] object navigation, [CanBeNull] object entityType)
=> string.Format(
GetString("NonVirtualNavigation", nameof(navigation), nameof(entityType)),
navigation, entityType);
/// <summary>
/// Navigation property '{navigation}' on entity type '{entityType}' is mapped without a CLR property. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.
/// </summary>
public static string FieldNavigation([CanBeNull] object navigation, [CanBeNull] object entityType)
=> string.Format(
GetString("FieldNavigation", nameof(navigation), nameof(entityType)),
navigation, entityType);
/// <summary>
/// Unable to create proxy for '{entityType}' because proxies are not enabled. Call 'DbContextOptionsBuilder.UseLazyLoadingProxies' to enable lazy-loading proxies.
/// </summary>
public static string ProxiesNotEnabled([CanBeNull] object entityType)
=> string.Format(
GetString("ProxiesNotEnabled", nameof(entityType)),
entityType);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
return value;
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<#
Session["ResourceFile"] = "ProxiesStrings.resx";
#>
<#@ include file="..\..\..\tools\Resources.tt" #>

Просмотреть файл

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ProxyServicesMissing" xml:space="preserve">
<value>UseLazyLoadingProxies requires AddEntityFrameworkProxies to be called on the internal service provider used.</value>
</data>
<data name="ItsASeal" xml:space="preserve">
<value>Entity type '{entityType}' is sealed. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.</value>
</data>
<data name="NonVirtualNavigation" xml:space="preserve">
<value>Navigation property '{navigation}' on entity type '{entityType}' is not virtual. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.</value>
</data>
<data name="FieldNavigation" xml:space="preserve">
<value>Navigation property '{navigation}' on entity type '{entityType}' is mapped without a CLR property. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.</value>
</data>
<data name="ProxiesNotEnabled" xml:space="preserve">
<value>Unable to create proxy for '{entityType}' because proxies are not enabled. Call 'DbContextOptionsBuilder.UseLazyLoadingProxies' to enable lazy-loading proxies.</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,34 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 interface IProxyFactory
{
/// <summary>
/// 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>
object CreateLazyLoadingProxy(
[NotNull] IEntityType entityType,
[NotNull] ILazyLoader loader,
[NotNull] object[] constructorArguments);
/// <summary>
/// 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>
object Create(
[NotNull] DbContext context,
[NotNull] Type entityClrType,
[NotNull] params object[] constructorArguments);
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// 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 Castle.DynamicProxy;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 class LazyLoadingInterceptor : IInterceptor
{
private readonly IEntityType _entityType;
private readonly ILazyLoader _loader;
/// <summary>
/// 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 LazyLoadingInterceptor(
[NotNull] IEntityType entityType,
[NotNull] ILazyLoader loader)
{
_entityType = entityType;
_loader = loader;
}
/// <summary>
/// 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 void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
if (methodName.StartsWith("get_", StringComparison.Ordinal))
{
var navigationName = methodName.Substring(4);
var navigation = _entityType.FindNavigation(navigationName);
if (navigation != null)
{
_loader.Load(invocation.Proxy, navigationName);
}
}
invocation.Proceed();
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 class ProxiesConventionSetBuilder : IConventionSetBuilder
{
private readonly IDbContextOptions _options;
private readonly IConstructorBindingFactory _constructorBindingFactory;
private readonly IProxyFactory _proxyFactory;
/// <summary>
/// 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 ProxiesConventionSetBuilder(
[NotNull] IDbContextOptions options,
[NotNull] IConstructorBindingFactory constructorBindingFactory,
[NotNull] IProxyFactory proxyFactory)
{
_options = options;
_constructorBindingFactory = constructorBindingFactory;
_proxyFactory = proxyFactory;
}
/// <summary>
/// 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 ConventionSet AddConventions(ConventionSet conventionSet)
{
conventionSet.ModelBuiltConventions.Add(
new ProxyBindingRewriter(
_proxyFactory,
_constructorBindingFactory,
_options.FindExtension<ProxiesOptionsExtension>()));
return conventionSet;
}
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 class ProxiesOptionsExtension : IDbContextOptionsExtension
{
private bool _useLazyLoadingProxies;
private string _logFragment;
/// <summary>
/// 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 ProxiesOptionsExtension()
{
}
/// <summary>
/// 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 ProxiesOptionsExtension([NotNull] ProxiesOptionsExtension copyFrom)
{
_useLazyLoadingProxies = copyFrom._useLazyLoadingProxies;
}
/// <summary>
/// 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 ProxiesOptionsExtension Clone() => new ProxiesOptionsExtension(this);
/// <summary>
/// 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 bool UseLazyLoadingProxies => _useLazyLoadingProxies;
/// <summary>
/// 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 ProxiesOptionsExtension WithLazyLoading(bool useLazyLoadingProxies = true)
{
var clone = Clone();
clone._useLazyLoadingProxies = useLazyLoadingProxies;
return clone;
}
/// <summary>
/// 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 long GetServiceProviderHashCode() => _useLazyLoadingProxies ? 541 : 0;
/// <summary>
/// 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 void Validate(IDbContextOptions options)
{
if (_useLazyLoadingProxies)
{
var internalServiceProvider = options.FindExtension<CoreOptionsExtension>()?.InternalServiceProvider;
if (internalServiceProvider != null)
{
using (var scope = internalServiceProvider.CreateScope())
{
if (scope.ServiceProvider
.GetService<IEnumerable<IConventionSetBuilder>>()
?.Any(s => s is ProxiesConventionSetBuilder) == false)
{
throw new InvalidOperationException(ProxiesStrings.ProxyServicesMissing);
}
}
}
}
}
/// <summary>
/// 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 bool ApplyServices(IServiceCollection services)
{
services.AddEntityFrameworkProxies();
return false;
}
/// <summary>
/// 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 string LogFragment
{
get
{
if (_logFragment == null)
{
_logFragment = _useLazyLoadingProxies
? "using lazy-loading proxies "
: "";
}
return _logFragment;
}
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// 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.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 class ProxyBindingRewriter : IModelBuiltConvention
{
private readonly ConstructorBindingConvention _directBindingConvention;
private readonly IProxyFactory _proxyFactory;
private readonly ProxiesOptionsExtension _options;
/// <summary>
/// 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 ProxyBindingRewriter(
[NotNull] IProxyFactory proxyFactory,
[NotNull] IConstructorBindingFactory bindingFactory,
[CanBeNull] ProxiesOptionsExtension options)
{
_directBindingConvention = new ConstructorBindingConvention(bindingFactory);
_proxyFactory = proxyFactory;
_options = options;
}
/// <summary>
/// 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 InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
if (_options?.UseLazyLoadingProxies == true)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
if (entityType.ClrType != null
&& !entityType.ClrType.IsAbstract)
{
if (entityType.ClrType.IsSealed)
{
throw new InvalidOperationException(ProxiesStrings.ItsASeal(entityType.DisplayName()));
}
var binding = (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding];
if (binding == null)
{
_directBindingConvention.Apply(modelBuilder);
}
binding = (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding];
entityType[CoreAnnotationNames.ConstructorBinding] = RewriteToFactoryBinding(binding);
foreach (var navigation in entityType.GetNavigations())
{
if (navigation.PropertyInfo == null)
{
throw new InvalidOperationException(
ProxiesStrings.FieldNavigation(navigation.Name, entityType.DisplayName()));
}
if (!navigation.PropertyInfo.GetMethod.IsVirtual)
{
throw new InvalidOperationException(
ProxiesStrings.NonVirtualNavigation(navigation.Name, entityType.DisplayName()));
}
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}
}
}
}
return modelBuilder;
}
private FactoryMethodConstructorBinding RewriteToFactoryBinding(ConstructorBinding currentBinding)
=> new FactoryMethodConstructorBinding(
_proxyFactory,
typeof(ProxyFactory).GetTypeInfo().GetDeclaredMethod(nameof(ProxyFactory.CreateLazyLoadingProxy)),
new List<ParameterBinding>
{
new EntityTypeParameterBinding(),
new ServiceParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader)),
new ObjectArrayParameterBinding(currentBinding.ParameterBindings)
}
);
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// 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 Castle.DynamicProxy;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Microsoft.EntityFrameworkCore.Proxies.Internal
{
/// <summary>
/// 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 class ProxyFactory : IProxyFactory
{
private readonly ProxyGenerator _generator = new ProxyGenerator();
/// <summary>
/// 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 object Create(
DbContext context,
Type entityClrType,
params object[] constructorArguments)
{
var entityType = context.Model.FindRuntimeEntityType(entityClrType);
if (entityType == null)
{
throw new InvalidOperationException(CoreStrings.EntityTypeNotFound(entityClrType.ShortDisplayName()));
}
return CreateLazyLoadingProxy(entityType, context.GetService<ILazyLoader>(), constructorArguments);
}
/// <summary>
/// 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 object CreateLazyLoadingProxy(
IEntityType entityType,
ILazyLoader loader,
object[] constructorArguments)
=> _generator.CreateClassProxy(
entityType.ClrType,
ProxyGenerationOptions.Default,
constructorArguments,
new LazyLoadingInterceptor(entityType, loader));
}
}

Просмотреть файл

@ -0,0 +1,112 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Proxies.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Extension methods related to use of proxies with Entity Framework Core.
/// </summary>
public static class ProxiesExtensions
{
/// <summary>
/// <para>
/// Turns on the creation of lazy-loading proxies.
/// </para>
/// <para>
/// Note that this requires appropriate services to be available in the EF internal service provider. Normally this
/// will happen automatically, but if the application is controlling the service provider, then a call to
/// <see cref="ProxiesServiceCollectionExtensions.AddEntityFrameworkProxies" /> may be needed.
/// </para>
/// </summary>
/// <param name="optionsBuilder">
/// The options builder, as passed to <see cref="DbContext.OnConfiguring" />
/// or exposed AddDbContext.
/// </param>
/// <param name="useLazyLoadingProxies"> <c>True</c> to use lazy-loading proxies; false to prevent their use. </param>
/// <returns> The same builder to allow method calls to be chained. </returns>
public static DbContextOptionsBuilder UseLazyLoadingProxies(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
bool useLazyLoadingProxies = true)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = optionsBuilder.Options.FindExtension<ProxiesOptionsExtension>()
?? new ProxiesOptionsExtension();
extension = extension.WithLazyLoading(useLazyLoadingProxies);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
return optionsBuilder;
}
/// <summary>
/// <para>
/// Turns on the creation of lazy-loading proxies.
/// </para>
/// <para>
/// Note that this requires appropriate services to be available in the EF internal service provider. Normally this
/// will happen automatically, but if the application is controlling the service provider, then a call to
/// <see cref="ProxiesServiceCollectionExtensions.AddEntityFrameworkProxies" /> may be needed.
/// </para>
/// </summary>
/// <typeparam name="TContext"> The <see cref="DbContext" /> type. </typeparam>
/// <param name="optionsBuilder">
/// The options builder, as passed to <see cref="DbContext.OnConfiguring" />
/// or exposed AddDbContext.
/// </param>
/// <param name="useLazyLoadingProxies"> <c>True</c> to use lazy-loading proxies; false to prevent their use. </param>
/// <returns> The same builder to allow method calls to be chained. </returns>
public static DbContextOptionsBuilder<TContext> UseLazyLoadingProxies<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
bool useLazyLoadingProxies = true)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseLazyLoadingProxies((DbContextOptionsBuilder)optionsBuilder, useLazyLoadingProxies);
/// <summary>
/// Creates a proxy instance for an entity type if proxy creation has been turned on.
/// </summary>
/// <param name="context"> The <see cref="DbContext" />. </param>
/// <param name="entityType"> The entity type for which a proxy is needed. </param>
/// <param name="constructorArguments"> Arguments to pass to the entity type constructor. </param>
/// <returns> The proxy instance. </returns>
public static object CreateProxy(
[NotNull] this DbContext context,
[NotNull] Type entityType,
[NotNull] params object[] constructorArguments)
{
Check.NotNull(context, nameof(context));
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(constructorArguments, nameof(constructorArguments));
var options = context.GetService<IDbContextOptions>().FindExtension<ProxiesOptionsExtension>();
if (options?.UseLazyLoadingProxies != true)
{
throw new InvalidOperationException(ProxiesStrings.ProxiesNotEnabled(entityType.ShortDisplayName()));
}
return context.GetService<IProxyFactory>().Create(context, entityType, constructorArguments);
}
/// <summary>
/// Creates a proxy instance for an entity type if proxy creation has been turned on.
/// </summary>
/// <typeparam name="TEntity"> The entity type for which a proxy is needed. </typeparam>
/// <param name="context"> The <see cref="DbContext" />. </param>
/// <param name="constructorArguments"> Arguments to pass to the entity type constructor. </param>
/// <returns> The proxy instance. </returns>
public static TEntity CreateProxy<TEntity>(
[NotNull] this DbContext context,
[NotNull] params object[] constructorArguments)
=> (TEntity)context.CreateProxy(typeof(TEntity), constructorArguments);
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Proxies.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// EntityFrameworkCore.Proxies extension methods for <see cref="IServiceCollection" />.
/// </summary>
public static class ProxiesServiceCollectionExtensions
{
/// <summary>
/// <para>
/// Adds the services required for proxy support in Entity Framework. You use this method when
/// using dependency injection in your application, such as with ASP.NET. For more information
/// on setting up dependency injection, see http://go.microsoft.com/fwlink/?LinkId=526890.
/// </para>
/// <para>
/// You only need to use this functionality when you want Entity Framework to resolve the services it uses
/// from an external dependency injection container. If you are not using an external
/// dependency injection container, Entity Framework will take care of creating the services it requires.
/// </para>
/// </summary>
/// <param name="serviceCollection"> The <see cref="IServiceCollection" /> to add services to. </param>
/// <returns>
/// The same service collection so that multiple calls can be chained.
/// </returns>
public static IServiceCollection AddEntityFrameworkProxies(
[NotNull] this IServiceCollection serviceCollection)
{
Check.NotNull(serviceCollection, nameof(serviceCollection));
new EntityFrameworkServicesBuilder(serviceCollection)
.TryAdd<IConventionSetBuilder, ProxiesConventionSetBuilder>()
.TryAddProviderSpecificServices(
b => b.TryAddSingleton<IProxyFactory, ProxyFactory>());
return serviceCollection;
}
}
}

Просмотреть файл

@ -54,9 +54,14 @@ namespace Microsoft.EntityFrameworkCore.Migrations
_model = new LazyRef<IModel>(
() =>
{
var modelBuilder = new ModelBuilder(
Dependencies.ConventionSetBuilder.AddConventions(
Dependencies.CoreConventionSetBuilder.CreateConventionSet()));
var conventionSet = Dependencies.CoreConventionSetBuilder.CreateConventionSet();
foreach (var conventionSetBuilder in Dependencies.ConventionSetBuilders)
{
conventionSet = conventionSetBuilder.AddConventions(conventionSet);
}
var modelBuilder = new ModelBuilder(conventionSet);
modelBuilder.Entity<HistoryRow>(
x =>
{

Просмотреть файл

@ -1,6 +1,9 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
@ -61,7 +64,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
/// <param name="migrationsSqlGenerator"> The SQL generator for Migrations operations. </param>
/// <param name="sqlGenerationHelper"> Helpers for generating update SQL. </param>
/// <param name="coreConventionSetBuilder"> The core convention set to use when creating the model. </param>
/// <param name="conventionSetBuilder"> The convention set to use when creating the model. </param>
/// <param name="conventionSetBuilders"> The convention sets to use when creating the model. </param>
public HistoryRepositoryDependencies(
[NotNull] IRelationalDatabaseCreator databaseCreator,
[NotNull] IRawSqlCommandBuilder rawSqlCommandBuilder,
@ -71,7 +74,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
[NotNull] IMigrationsSqlGenerator migrationsSqlGenerator,
[NotNull] ISqlGenerationHelper sqlGenerationHelper,
[NotNull] ICoreConventionSetBuilder coreConventionSetBuilder,
[NotNull] IConventionSetBuilder conventionSetBuilder)
[NotNull] IEnumerable<IConventionSetBuilder> conventionSetBuilders)
{
Check.NotNull(databaseCreator, nameof(databaseCreator));
Check.NotNull(rawSqlCommandBuilder, nameof(rawSqlCommandBuilder));
@ -81,7 +84,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
Check.NotNull(migrationsSqlGenerator, nameof(migrationsSqlGenerator));
Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper));
Check.NotNull(coreConventionSetBuilder, nameof(coreConventionSetBuilder));
Check.NotNull(conventionSetBuilder, nameof(conventionSetBuilder));
Check.NotNull(conventionSetBuilders, nameof(conventionSetBuilders));
DatabaseCreator = databaseCreator;
RawSqlCommandBuilder = rawSqlCommandBuilder;
@ -91,7 +94,10 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator = migrationsSqlGenerator;
SqlGenerationHelper = sqlGenerationHelper;
CoreConventionSetBuilder = coreConventionSetBuilder;
ConventionSetBuilder = conventionSetBuilder;
ConventionSetBuilders = conventionSetBuilders;
#pragma warning disable 618
ConventionSetBuilder = new CompositeConventionSetBuilder((IReadOnlyList<IConventionSetBuilder>)ConventionSetBuilders);
#pragma warning restore 618
}
/// <summary>
@ -137,8 +143,14 @@ namespace Microsoft.EntityFrameworkCore.Migrations
/// <summary>
/// The convention set to use when creating the model.
/// </summary>
[Obsolete("Use ConventionSetBuilders")]
public IConventionSetBuilder ConventionSetBuilder { get; }
/// <summary>
/// The convention set to use when creating the model.
/// </summary>
public IEnumerable<IConventionSetBuilder> ConventionSetBuilders { get; }
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
@ -154,7 +166,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -171,7 +183,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -188,7 +200,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -205,7 +217,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -222,7 +234,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -239,7 +251,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
migrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -256,7 +268,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
sqlGenerationHelper,
CoreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
@ -273,13 +285,14 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
coreConventionSetBuilder,
ConventionSetBuilder);
ConventionSetBuilders);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="conventionSetBuilder"> The convention set to use when creating the model. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
[Obsolete("Use With(IEnumerable<IConventionSetBuilder>)")]
public HistoryRepositoryDependencies With([NotNull] IConventionSetBuilder conventionSetBuilder)
=> new HistoryRepositoryDependencies(
DatabaseCreator,
@ -290,6 +303,23 @@ namespace Microsoft.EntityFrameworkCore.Migrations
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
conventionSetBuilder);
new[] { conventionSetBuilder });
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="conventionSetBuilders"> The convention sets to use when creating the model. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public HistoryRepositoryDependencies With([NotNull] IEnumerable<IConventionSetBuilder> conventionSetBuilders)
=> new HistoryRepositoryDependencies(
DatabaseCreator,
RawSqlCommandBuilder,
Connection,
Options,
ModelDiffer,
MigrationsSqlGenerator,
SqlGenerationHelper,
CoreConventionSetBuilder,
conventionSetBuilders);
}
}

Просмотреть файл

@ -55,7 +55,7 @@ namespace Microsoft.EntityFrameworkCore
}
protected virtual IConventionSetBuilder CreateConventionSetBuilder(DbContext context)
=> context.GetService<IConventionSetBuilder>();
=> new CompositeConventionSetBuilder(context.GetService<IEnumerable<IConventionSetBuilder>>().ToList());
protected virtual DiagnosticsLogger<DbLoggerCategory.Model> CreateLogger()
{

Просмотреть файл

@ -17,6 +17,7 @@
<ItemGroup>
<ProjectReference Include="..\EFCore\EFCore.csproj" />
<ProjectReference Include="..\EFCore.Proxies\EFCore.Proxies.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1217,7 +1217,7 @@ namespace Microsoft.EntityFrameworkCore
}
[Fact]
public virtual void Lazy_load_collection_for_detached_throws()
public virtual void Lazy_load_collection_for_detached_is_no_op()
{
using (var context = CreateContext(lazyLoadingEnabled: true))
{
@ -1225,15 +1225,12 @@ namespace Microsoft.EntityFrameworkCore
context.Entry(parent).State = EntityState.Detached;
Assert.Equal(
CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
Assert.Throws<InvalidOperationException>(
() => parent.Children).Message);
Assert.Null(parent.Children);
}
}
[Fact]
public virtual void Lazy_load_reference_to_principal_for_detached_throws()
public virtual void Lazy_load_reference_to_principal_for_detached_is_no_op()
{
using (var context = CreateContext(lazyLoadingEnabled: true))
{
@ -1241,15 +1238,12 @@ namespace Microsoft.EntityFrameworkCore
context.Entry(child).State = EntityState.Detached;
Assert.Equal(
CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
Assert.Throws<InvalidOperationException>(
() => child.Parent).Message);
Assert.Null(child.Parent);
}
}
[Fact]
public virtual void Lazy_load_reference_to_dependent_for_detached_throws()
public virtual void Lazy_load_reference_to_dependent_for_detached_is_no_op()
{
using (var context = CreateContext(lazyLoadingEnabled: true))
{
@ -1257,10 +1251,7 @@ namespace Microsoft.EntityFrameworkCore
context.Entry(parent).State = EntityState.Detached;
Assert.Equal(
CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
Assert.Throws<InvalidOperationException>(
() => parent.Single).Message);
Assert.Null(parent.Single);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -45,7 +45,9 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities
var serviceProperties = typeof(TDependencies).GetTypeInfo()
.DeclaredProperties
.Where(p => !ignoreProperties.Contains(p.Name))
.Where(
p => !ignoreProperties.Contains(p.Name)
&& p.CustomAttributes.All(a => a.AttributeType != typeof(ObsoleteAttribute)))
.ToList();
Assert.Equal(constructorParameters.Length, serviceProperties.Count);
@ -54,7 +56,8 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities
{
var withMethod = typeof(TDependencies).GetTypeInfo().DeclaredMethods
.Single(
m => m.Name == "With"
m => m.CustomAttributes.All(a => a.AttributeType != typeof(ObsoleteAttribute))
&& m.Name == "With"
&& m.GetParameters()[0].ParameterType == serviceType);
var clone = withMethod.Invoke(dependencies, new[] { services2.GetService(serviceType) });
@ -189,7 +192,7 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities
{
var contextServices = CreateContextServices();
var conventionSetBuilder = contextServices.GetRequiredService<IConventionSetBuilder>();
var conventionSetBuilder = new CompositeConventionSetBuilder(contextServices.GetRequiredService<IEnumerable<IConventionSetBuilder>>().ToList());
var conventionSet = contextServices.GetRequiredService<ICoreConventionSetBuilder>().CreateConventionSet();
conventionSet = conventionSetBuilder == null
? conventionSet

Просмотреть файл

@ -104,7 +104,6 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
{ typeof(IChangeDetector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IDbContextServices), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IValueGeneratorSelector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IExpressionPrinter), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IExecutionStrategyFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IAsyncQueryProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
@ -127,6 +126,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
{ typeof(IEntityQueryableExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IEntityQueryModelVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IEntityStateListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(INavigationListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IKeyListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },

Просмотреть файл

@ -347,16 +347,24 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
foreach (var entityType in model.GetEntityTypes())
{
var properties = new HashSet<IPropertyBase>(
entityType
.GetDeclaredProperties()
.Cast<IPropertyBase>()
.Concat(entityType.GetDeclaredNavigations())
.Where(p => !p.IsShadowProperty));
var constructorBinding = (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding];
foreach (var propertyBase in entityType
.GetDeclaredProperties()
.Cast<IPropertyBase>()
.Concat(entityType.GetDeclaredNavigations())
.Where(
e => !e.IsShadowProperty
&& (constructorBinding == null
|| constructorBinding.ParameterBindings.All(p => p.ConsumedProperty != e))))
if (constructorBinding != null)
{
foreach (var consumedProperty in constructorBinding.ParameterBindings.SelectMany(p => p.ConsumedProperties))
{
properties.Remove(consumedProperty);
}
}
foreach (var propertyBase in properties)
{
if (!propertyBase.TryGetMemberInfo(
forConstruction: true,

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@ -70,7 +71,7 @@ namespace Microsoft.EntityFrameworkCore.Internal
return _scopedProvider.GetService<IModelSource>().GetModel(
_currentContext.Context,
_scopedProvider.GetService<IConventionSetBuilder>(),
new CompositeConventionSetBuilder(_scopedProvider.GetService<IEnumerable<IConventionSetBuilder>>().ToList()),
_scopedProvider.GetService<IModelValidator>());
}
finally

Просмотреть файл

@ -57,12 +57,16 @@ namespace Microsoft.EntityFrameworkCore.Internal
}
else if (Context.ChangeTracker.LazyLoadingEnabled)
{
var entry = Context.Entry(entity).Navigation(navigationName);
if (!entry.IsLoaded)
var entityEntry = Context.Entry(entity);
if (entityEntry.State != EntityState.Detached)
{
Logger.NavigationLazyLoading(Context, entity, navigationName);
var entry = entityEntry.Navigation(navigationName);
if (!entry.IsLoaded)
{
Logger.NavigationLazyLoading(Context, entity, navigationName);
entry.Load();
entry.Load();
}
}
}
}

Просмотреть файл

@ -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.Collections.Generic;
using JetBrains.Annotations;
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <summary>
/// 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 class CompositeConventionSetBuilder : IConventionSetBuilder
{
private readonly IReadOnlyList<IConventionSetBuilder> _builders;
/// <summary>
/// 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 CompositeConventionSetBuilder([NotNull] IReadOnlyList<IConventionSetBuilder> builders)
{
_builders = builders;
}
/// <summary>
/// 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 ConventionSet AddConventions(ConventionSet conventionSet)
{
foreach (var builder in _builders)
{
builder.AddConventions(conventionSet);
}
return conventionSet;
}
}
}

Просмотреть файл

@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public ContextParameterBinding([NotNull] Type contextType)
: base(contextType, null)
: base(contextType)
{
ContextType = contextType;
}

Просмотреть файл

@ -15,7 +15,7 @@ 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.
/// </summary>
public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter)
public override ParameterBinding TryBindParameter(IMutableEntityType entityType, ParameterInfo parameter)
=> typeof(DbContext).IsAssignableFrom(parameter.ParameterType)
? new ContextParameterBinding(parameter.ParameterType)
: null;

Просмотреть файл

@ -178,10 +178,21 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
constructorBinding.CreateConstructorExpression(bindingInfo))
};
var properties = new HashSet<IProperty>(
entityType
.GetProperties()
.Where(p => !p.IsShadowProperty));
foreach (var consumedProperty in constructorBinding
.ParameterBindings
.SelectMany(p => p.ConsumedProperties)
.OfType<IProperty>())
{
properties.Remove(consumedProperty);
}
blockExpressions.AddRange(
from property in entityType.GetProperties().Where(
p => !p.IsShadowProperty
&& constructorBinding.ParameterBindings.All(b => b.ConsumedProperty != p))
from property in properties
let targetMember = Expression.MakeMemberAccess(
instanceVariable,
property.GetMemberInfo(forConstruction: true, forSet: true))

Просмотреть файл

@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public EntityTypeParameterBinding()
: base(typeof(IEntityType), null)
: base(typeof(IEntityType))
{
}
@ -25,6 +25,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Expression BindToParameter(ParameterBindingInfo bindingInfo)
=> Expression.Constant(bindingInfo.EnityType, typeof(IEntityType));
=> Expression.Constant(bindingInfo.EntityType, typeof(IEntityType));
}
}

Просмотреть файл

@ -15,7 +15,7 @@ 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.
/// </summary>
public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter)
public override ParameterBinding TryBindParameter(IMutableEntityType entityType, ParameterInfo parameter)
=> parameter.ParameterType == typeof(IEntityType)
? new EntityTypeParameterBinding()
: null;

Просмотреть файл

@ -1,7 +1,6 @@
// 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;
@ -62,9 +61,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
_factoryMethod,
arguments);
if (_factoryMethod.ReturnType != bindingInfo.EnityType.ClrType)
if (_factoryMethod.ReturnType != bindingInfo.EntityType.ClrType)
{
expression = Expression.Convert(expression, bindingInfo.EnityType.ClrType);
expression = Expression.Convert(expression, bindingInfo.EntityType.ClrType);
}
return expression;

Просмотреть файл

@ -18,11 +18,11 @@ 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.
/// </summary>
public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter)
public override ParameterBinding TryBindParameter(IMutableEntityType entityType, ParameterInfo parameter)
{
if (parameter.ParameterType == typeof(ILazyLoader))
{
EnsureFieldAccess(enityType);
EnsureFieldAccess(entityType);
return new ServiceParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader));
}
@ -30,7 +30,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
if (parameter.ParameterType == typeof(Action<object, string>)
&& parameter.Name.Equals("lazyLoader", StringComparison.OrdinalIgnoreCase))
{
EnsureFieldAccess(enityType);
EnsureFieldAccess(entityType);
return new ServiceMethodParameterBinding(typeof(Action<object, string>), typeof(ILazyLoader), _loadMethod);
}
@ -38,9 +38,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
return null;
}
private static void EnsureFieldAccess(IMutableEntityType enityType)
private static void EnsureFieldAccess(IMutableEntityType entityType)
{
foreach (var navigation in enityType.GetNavigations())
foreach (var navigation in entityType.GetNavigations())
{
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}

Просмотреть файл

@ -0,0 +1,50 @@
// 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
{
/// <summary>
/// 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 class ObjectArrayParameterBinding : ParameterBinding
{
private readonly IReadOnlyList<ParameterBinding> _bindings;
/// <summary>
/// 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 ObjectArrayParameterBinding([NotNull] IReadOnlyList<ParameterBinding> bindings)
: base(typeof(object[]), bindings.SelectMany(b => b.ConsumedProperties).ToArray())
{
_bindings = bindings;
}
/// <summary>
/// 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 override Expression BindToParameter(ParameterBindingInfo bindingInfo)
=> Expression.NewArrayInit(
typeof(object),
_bindings.Select(
b =>
{
var expression = b.BindToParameter(bindingInfo);
if (expression.Type.GetTypeInfo().IsValueType)
{
expression = Expression.Convert(expression, typeof(object));
}
return expression;
}));
}
}

Просмотреть файл

@ -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.Collections.Generic;
using System.Linq.Expressions;
using JetBrains.Annotations;
@ -19,10 +20,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// </summary>
protected ParameterBinding(
[NotNull] Type parameterType,
[CanBeNull] IPropertyBase consumedProperty)
[NotNull] params IPropertyBase[] consumedProperties)
{
ParameterType = parameterType;
ConsumedProperty = consumedProperty;
ConsumedProperties = consumedProperties;
}
/// <summary>
@ -35,7 +36,7 @@ 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.
/// </summary>
public virtual IPropertyBase ConsumedProperty { get; }
public virtual IReadOnlyList<IPropertyBase> ConsumedProperties { get; }
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used

Просмотреть файл

@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public abstract ParameterBinding TryBindParameter(
[NotNull] IMutableEntityType enityType,
[NotNull] IMutableEntityType entityType,
[NotNull] ParameterInfo parameter);
}
}

Просмотреть файл

@ -19,13 +19,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public ParameterBindingInfo(
[NotNull] IEntityType enityType,
[NotNull] IEntityType entityType,
[NotNull] Expression valueBufferExpression,
[NotNull] Expression contextExpression,
[CanBeNull] int[] indexMap)
{
_indexMap = indexMap;
EnityType = enityType;
EntityType = entityType;
ValueBufferExpression = valueBufferExpression;
ContextExpression = contextExpression;
}
@ -34,7 +34,7 @@ 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.
/// </summary>
public IEntityType EnityType { get; }
public IEntityType EntityType { get; }
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used

Просмотреть файл

@ -26,10 +26,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Expression BindToParameter(ParameterBindingInfo bindingInfo)
=> Expression.Call(
EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(ConsumedProperty.ClrType),
{
var property = ConsumedProperties[0];
return Expression.Call(
EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(property.ClrType),
bindingInfo.ValueBufferExpression,
Expression.Constant(bindingInfo.GetValueBufferIndex(ConsumedProperty)),
Expression.Constant(ConsumedProperty, typeof(IPropertyBase)));
Expression.Constant(bindingInfo.GetValueBufferIndex(property)),
Expression.Constant(property, typeof(IPropertyBase)));
}
}
}

Просмотреть файл

@ -19,11 +19,11 @@ 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.
/// </summary>
public override ParameterBinding TryBindParameter(IMutableEntityType enityType, ParameterInfo parameter)
public override ParameterBinding TryBindParameter(IMutableEntityType entityType, ParameterInfo parameter)
{
var candidateNames = GetCandidatePropertyNames(parameter);
return enityType.GetProperties().Where(
return entityType.GetProperties().Where(
p => p.ClrType == parameter.ParameterType
&& candidateNames.Any(c => c.Equals(p.Name, StringComparison.Ordinal)))
.Select(p => new PropertyParameterBinding(p)).FirstOrDefault();

Просмотреть файл

@ -26,7 +26,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
public ServiceParameterBinding(
[NotNull] Type parameterType,
[NotNull] Type serviceType)
: base(parameterType, null)
: base(parameterType)
{
ServiceType = serviceType;
}

Просмотреть файл

@ -0,0 +1,20 @@
// 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.TestUtilities;
namespace Microsoft.EntityFrameworkCore
{
public class LazyLoadProxyInMemoryTest : LazyLoadProxyTestBase<LazyLoadProxyInMemoryTest.LoadInMemoryFixture>
{
public LazyLoadProxyInMemoryTest(LoadInMemoryFixture fixture)
: base(fixture)
{
}
public class LoadInMemoryFixture : LoadFixtureBase
{
protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance;
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// 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;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore
{
public class ApiConsistencyTest : ApiConsistencyTestBase
{
protected override void AddServices(ServiceCollection serviceCollection)
{
serviceCollection.AddEntityFrameworkInMemoryDatabase();
}
protected override Assembly TargetAssembly => typeof(ProxiesExtensions).GetTypeInfo().Assembly;
}
}

Просмотреть файл

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<AssemblyName>Microsoft.EntityFrameworkCore.Proxies.Tests</AssemblyName>
<RootNamespace>Microsoft.EntityFrameworkCore</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="..\xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\EFCore.Proxies\EFCore.Proxies.csproj" />
<ProjectReference Include="..\EFCore.Tests\EFCore.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="TestUtilities\FakeProvider\FakeDbCommand.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,456 @@
// 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 Castle.DynamicProxy;
using Castle.DynamicProxy.Generators;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.EntityFrameworkCore
{
public class LazyLoadingProxyTests
{
[Fact]
public void Materialization_uses_parameterless_constructor()
{
using (var context = new NeweyContext(nameof(Materialization_uses_parameterless_constructor)))
{
context.Add(new March82GGtp());
context.SaveChanges();
}
using (var context = new NeweyContext(nameof(Materialization_uses_parameterless_constructor)))
{
Assert.Same(typeof(March82GGtp), context.Set<March82GGtp>().Single().GetType().BaseType);
}
}
[Fact]
public void Materialization_uses_parameterized_constructor()
{
using (var context = new NeweyContext(nameof(Materialization_uses_parameterized_constructor)))
{
context.Add(new March881(77, "Leyton House"));
context.SaveChanges();
}
using (var context = new NeweyContext(nameof(Materialization_uses_parameterized_constructor)))
{
var proxy = context.Set<March881>().Single();
Assert.Same(typeof(March881), proxy.GetType().BaseType);
Assert.Equal(77, proxy.Id);
Assert.Equal("Leyton House", proxy.Sponsor);
}
}
[Fact]
public void Materialization_uses_parameterized_constructor_taking_context()
{
using (var context = new NeweyContext(nameof(Materialization_uses_parameterized_constructor_taking_context)))
{
context.Add(new WilliamsFw14(context, 6, "Canon"));
context.SaveChanges();
}
using (var context = new NeweyContext(nameof(Materialization_uses_parameterized_constructor_taking_context)))
{
var proxy = context.Set<WilliamsFw14>().Single();
Assert.Same(typeof(WilliamsFw14), proxy.GetType().BaseType);
Assert.Same(context, proxy.Context);
Assert.Equal(6, proxy.Id);
Assert.Equal("Canon", proxy.Sponsor);
}
}
[Fact]
public void CreateProxy_uses_parameterless_constructor()
{
using (var context = new NeweyContext())
{
Assert.Same(typeof(March82GGtp), context.CreateProxy<March82GGtp>().GetType().BaseType);
}
}
[Fact]
public void CreateProxy_uses_parameterized_constructor()
{
using (var context = new NeweyContext())
{
var proxy = context.CreateProxy<March881>(77, "Leyton House");
Assert.Same(typeof(March881), proxy.GetType().BaseType);
Assert.Equal(77, proxy.Id);
Assert.Equal("Leyton House", proxy.Sponsor);
}
}
[Fact]
public void CreateProxy_uses_parameterized_constructor_taking_context()
{
using (var context = new NeweyContext())
{
var proxy = context.CreateProxy<WilliamsFw14>(context, 6, "Canon");
Assert.Same(typeof(WilliamsFw14), proxy.GetType().BaseType);
Assert.Same(context, proxy.Context);
Assert.Equal(6, proxy.Id);
Assert.Equal("Canon", proxy.Sponsor);
}
}
[Fact]
public void Proxies_only_created_if_Use_called()
{
using (var context = new NeweyContext(nameof(Proxies_only_created_if_Use_called), false))
{
context.Add(new March82GGtp());
context.SaveChanges();
}
using (var context = new NeweyContext(nameof(Proxies_only_created_if_Use_called), false))
{
Assert.Same(typeof(March82GGtp), context.Set<March82GGtp>().Single().GetType());
}
using (var context = new NeweyContext(nameof(Proxies_only_created_if_Use_called)))
{
Assert.Same(typeof(March82GGtp), context.Set<March82GGtp>().Single().GetType().BaseType);
}
}
[Fact]
public void Proxy_services_must_be_available()
{
var withoutProxies = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
var withProxies = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.AddEntityFrameworkProxies()
.BuildServiceProvider();
using (var context = new NeweyContext(withoutProxies, nameof(Proxy_services_must_be_available), false))
{
context.Add(new March82GGtp());
context.SaveChanges();
}
using (var context = new NeweyContext(withoutProxies, nameof(Proxy_services_must_be_available), false))
{
Assert.Same(typeof(March82GGtp), context.Set<March82GGtp>().Single().GetType());
}
using (var context = new NeweyContext(withProxies, nameof(Proxy_services_must_be_available)))
{
Assert.Same(typeof(March82GGtp), context.Set<March82GGtp>().Single().GetType().BaseType);
}
using (var context = new NeweyContext(withoutProxies, nameof(Proxy_services_must_be_available)))
{
Assert.Equal(
ProxiesStrings.ProxyServicesMissing,
Assert.Throws<InvalidOperationException>(
() => context.Model).Message);
}
}
[Fact]
public void Throws_if_sealed_class()
{
using (var context = new NeweyContextN1())
{
Assert.Equal(
ProxiesStrings.ItsASeal(nameof(McLarenMp418)),
Assert.Throws<InvalidOperationException>(
() => context.Model).Message);
}
}
[Fact]
public void Throws_if_non_virtual_navigation()
{
using (var context = new NeweyContextN2())
{
Assert.Equal(
ProxiesStrings.NonVirtualNavigation(nameof(McLarenMp419.SelfRef), nameof(McLarenMp419)),
Assert.Throws<InvalidOperationException>(
() => context.Model).Message);
}
}
[Fact]
public void Throws_if_no_field_found()
{
using (var context = new NeweyContextN3())
{
Assert.Equal(
CoreStrings.NoBackingFieldLazyLoading(nameof(MarchCg901.SelfRef), nameof(MarchCg901)),
Assert.Throws<InvalidOperationException>(
() => context.Model).Message);
}
}
[Fact]
public void Throws_if_type_not_available_to_Castle()
{
using (var context = new NeweyContextN4())
{
Assert.Throws<GeneratorException>(() => context.CreateProxy<McLarenMp421>());
}
}
[Fact]
public void Throws_if_constructor_not_available_to_Castle()
{
using (var context = new NeweyContextN5())
{
Assert.Throws<InvalidProxyConstructorArgumentsException>(() => context.CreateProxy<RedBullRb3>());
}
}
[Fact]
public void CreateProxy_throws_if_constructor_args_do_not_match()
{
using (var context = new NeweyContext())
{
Assert.Throws<InvalidProxyConstructorArgumentsException>(() => context.CreateProxy<March881>(77, 88));
}
}
[Fact]
public void CreateProxy_throws_if_wrong_number_of_constructor_args()
{
using (var context = new NeweyContext())
{
Assert.Throws<InvalidProxyConstructorArgumentsException>(() => context.CreateProxy<March881>(77, 88, 99));
}
}
[Fact]
public void Throws_if_create_proxy_for_non_mapped_type()
{
using (var context = new NeweyContextN4())
{
Assert.Equal(
CoreStrings.EntityTypeNotFound(nameof(March82GGtp)),
Assert.Throws<InvalidOperationException>(
() => context.CreateProxy<March82GGtp>()).Message);
}
}
[Fact]
public void Throws_if_create_proxy_when_proxies_not_used()
{
using (var context = new NeweyContextN6())
{
Assert.Equal(
ProxiesStrings.ProxiesNotEnabled(nameof(RedBullRb3)),
Assert.Throws<InvalidOperationException>(
() => context.CreateProxy<RedBullRb3>()).Message);
}
}
[Fact]
public void Throws_if_create_proxy_when_proxies_not_enabled()
{
using (var context = new NeweyContextN7())
{
Assert.Equal(
ProxiesStrings.ProxiesNotEnabled(nameof(RedBullRb3)),
Assert.Throws<InvalidOperationException>(
() => context.CreateProxy<RedBullRb3>()).Message);
}
}
public class March82GGtp
{
public int Id { get; set; }
}
public class March881
{
public March881(int id, string sponsor)
{
Id = id;
Sponsor = sponsor;
}
public int Id { get; }
public string Sponsor { get; }
}
public class WilliamsFw14
{
public WilliamsFw14(DbContext context, int id, string sponsor)
{
Context = context;
Id = id;
Sponsor = sponsor;
}
public DbContext Context { get; }
public int Id { get; }
public string Sponsor { get; }
}
private class NeweyContext : DbContext
{
private readonly IServiceProvider _internalServiceProvider;
private static readonly InMemoryDatabaseRoot _dbRoot = new InMemoryDatabaseRoot();
private readonly bool _useProxies;
private readonly string _dbName;
public NeweyContext(string dbName = null, bool useProxies = true)
{
_dbName = dbName;
_useProxies = useProxies;
}
public NeweyContext(IServiceProvider internalServiceProvider, string dbName = null, bool useProxies = true)
: this(dbName, useProxies)
{
_internalServiceProvider = internalServiceProvider;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_useProxies)
{
optionsBuilder.UseLazyLoadingProxies();
}
if (_internalServiceProvider != null)
{
optionsBuilder.UseInternalServiceProvider(_internalServiceProvider);
}
optionsBuilder.UseInMemoryDatabase(_dbName ?? nameof(NeweyContext), _dbRoot);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<March82GGtp>();
modelBuilder.Entity<March881>(
b =>
{
b.Property(e => e.Id);
b.Property(e => e.Sponsor);
});
modelBuilder.Entity<WilliamsFw14>(
b =>
{
b.Property(e => e.Id);
b.Property(e => e.Sponsor);
});
}
}
public sealed class McLarenMp418
{
public int Id { get; set; }
}
private class NeweyContextN : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
private class NeweyContextN1 : NeweyContextN
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<McLarenMp418>();
}
public class McLarenMp419
{
public int Id { get; set; }
public McLarenMp419 SelfRef { get; set; }
}
private class NeweyContextN2 : NeweyContextN
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<McLarenMp419>();
}
public class MarchCg901
{
private MarchCg901 _hiddenBackingField;
public int Id { get; set; }
// ReSharper disable once ConvertToAutoProperty
public virtual MarchCg901 SelfRef
{
get => _hiddenBackingField;
set => _hiddenBackingField = value;
}
}
private class NeweyContextN3 : NeweyContextN
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<MarchCg901>();
}
internal class McLarenMp421
{
public int Id { get; set; }
}
private class NeweyContextN4 : NeweyContextN
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<McLarenMp421>();
}
public class RedBullRb3
{
internal RedBullRb3()
{
}
public int Id { get; set; }
}
private class NeweyContextN5 : NeweyContextN
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<RedBullRb3>();
}
private class NeweyContextN6 : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseInMemoryDatabase(Guid.NewGuid().ToString());
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<March82GGtp>();
}
private class NeweyContextN7 : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies(false)
.UseInMemoryDatabase(Guid.NewGuid().ToString());
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<March82GGtp>();
}
}
}

Просмотреть файл

@ -0,0 +1,448 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Microsoft.EntityFrameworkCore
{
public class LazyLoadProxySqlServerTest : LazyLoadProxyTestBase<LazyLoadProxySqlServerTest.LoadSqlServerFixture>
{
public LazyLoadProxySqlServerTest(LoadSqlServerFixture fixture)
: base(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);
if (!async)
{
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);
}
}
protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear();
protected override void RecordLog() => Sql = Fixture.TestSqlLoggerFactory.Sql;
private string Sql { get; set; }
public class LoadSqlServerFixture : LoadFixtureBase
{
public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService<ILoggerFactory>();
protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance;
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
c => c
.Log(RelationalEventId.QueryClientEvaluationWarning));
}
}
}

Просмотреть файл

@ -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 Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore
{
public abstract class ProxyGraphUpdatesSqlServerTest
{
public abstract class ProxyGraphUpdatesSqlServerTestBase<TFixture> : ProxyGraphUpdatesTestBase<TFixture>
where TFixture : ProxyGraphUpdatesSqlServerTestBase<TFixture>.ProxyGraphUpdatesSqlServerFixtureBase, new()
{
protected ProxyGraphUpdatesSqlServerTestBase(TFixture fixture)
: base(fixture)
{
}
protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());
public abstract class ProxyGraphUpdatesSqlServerFixtureBase : ProxyGraphUpdatesFixtureBase
{
public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService<ILoggerFactory>();
protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance;
}
}
public class LazyLoading : ProxyGraphUpdatesSqlServerTestBase<LazyLoading.ProxyGraphUpdatesWithLazyLoadingSqlServerFixture>
{
public LazyLoading(ProxyGraphUpdatesWithLazyLoadingSqlServerFixture fixture)
: base(fixture)
{
}
public class ProxyGraphUpdatesWithLazyLoadingSqlServerFixture : ProxyGraphUpdatesSqlServerFixtureBase
{
protected override string StoreName { get; } = "ProxyGraphLazyLoadingUpdatesTest";
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder.UseLazyLoadingProxies());
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection.AddEntityFrameworkProxies());
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
modelBuilder.ForSqlServerUseIdentityColumns();
base.OnModelCreating(modelBuilder, context);
}
}
}
}
}

Просмотреть файл

@ -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 Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.TestUtilities;
namespace Microsoft.EntityFrameworkCore
{
public class LazyLoadProxySqliteTest : LazyLoadProxyTestBase<LazyLoadProxySqliteTest.LoadSqliteFixture>
{
public LazyLoadProxySqliteTest(LoadSqliteFixture fixture)
: base(fixture)
{
}
public class LoadSqliteFixture : LoadFixtureBase
{
protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance;
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
c => c
.Log(RelationalEventId.QueryClientEvaluationWarning));
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
@ -43,9 +44,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
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);
Assert.Equal("Title", bindings[0].ConsumedProperties.First().Name);
Assert.Equal("Shadow", bindings[1].ConsumedProperties.First().Name);
Assert.Equal("Id", bindings[2].ConsumedProperties.First().Name);
}
private class BlogSeveral : Blog
@ -120,15 +121,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("FooBaar5", parameters[7].Name);
Assert.Equal("FooBaar6", parameters[8].Name);
Assert.Equal("FooBaar1", bindings[0].ConsumedProperty.Name);
Assert.Equal("fooBaar2", bindings[1].ConsumedProperty.Name);
Assert.Equal("_fooBaar3", bindings[2].ConsumedProperty.Name);
Assert.Equal("m_fooBaar4", bindings[3].ConsumedProperty.Name);
Assert.Equal("_FooBaar5", bindings[4].ConsumedProperty.Name);
Assert.Equal("m_FooBaar6", bindings[5].ConsumedProperty.Name);
Assert.Equal("FooBaar1", bindings[6].ConsumedProperty.Name);
Assert.Equal("_FooBaar5", bindings[7].ConsumedProperty.Name);
Assert.Equal("m_FooBaar6", bindings[8].ConsumedProperty.Name);
Assert.Equal("FooBaar1", bindings[0].ConsumedProperties.First().Name);
Assert.Equal("fooBaar2", bindings[1].ConsumedProperties.First().Name);
Assert.Equal("_fooBaar3", bindings[2].ConsumedProperties.First().Name);
Assert.Equal("m_fooBaar4", bindings[3].ConsumedProperties.First().Name);
Assert.Equal("_FooBaar5", bindings[4].ConsumedProperties.First().Name);
Assert.Equal("m_FooBaar6", bindings[5].ConsumedProperties.First().Name);
Assert.Equal("FooBaar1", bindings[6].ConsumedProperties.First().Name);
Assert.Equal("_FooBaar5", bindings[7].ConsumedProperties.First().Name);
Assert.Equal("m_FooBaar6", bindings[8].ConsumedProperties.First().Name);
}
private class BlogSpanner : Blog
@ -166,8 +167,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
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);
Assert.Equal("_content", bindings[0].ConsumedProperties.First().Name);
Assert.Equal("m_follows", bindings[1].ConsumedProperties.First().Name);
}
private class BlogWierdScience : Blog
@ -194,10 +195,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("context", parameters[1].Name);
Assert.IsType<PropertyParameterBinding>(bindings[0]);
Assert.Equal("Id", bindings[0].ConsumedProperty.Name);
Assert.Equal("Id", bindings[0].ConsumedProperties.First().Name);
Assert.IsType<ContextParameterBinding>(bindings[1]);
Assert.Null(bindings[1].ConsumedProperty);
Assert.Empty(bindings[1].ConsumedProperties);
Assert.Same(typeof(DbContext), ((ContextParameterBinding)bindings[1]).ContextType);
}
@ -224,7 +225,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("context", parameters[0].Name);
Assert.IsType<ContextParameterBinding>(bindings[0]);
Assert.Null(bindings[0].ConsumedProperty);
Assert.Empty(bindings[0].ConsumedProperties);
Assert.Same(typeof(TypedContext), ((ContextParameterBinding)bindings[0]).ContextType);
}
@ -251,7 +252,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("loader", parameters[0].Name);
Assert.IsType<ServiceParameterBinding>(bindings[0]);
Assert.Null(bindings[0].ConsumedProperty);
Assert.Empty(bindings[0].ConsumedProperties);
Assert.Same(typeof(ILazyLoader), ((ServiceParameterBinding)bindings[0]).ServiceType);
}
@ -278,7 +279,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("lazyLoader", parameters[0].Name);
Assert.IsType<ServiceMethodParameterBinding>(bindings[0]);
Assert.Null(bindings[0].ConsumedProperty);
Assert.Empty(bindings[0].ConsumedProperties);
Assert.Same(typeof(ILazyLoader), ((ServiceMethodParameterBinding)bindings[0]).ServiceType);
}
@ -305,7 +306,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
Assert.Equal("entityType", parameters[0].Name);
Assert.IsType<EntityTypeParameterBinding>(bindings[0]);
Assert.Null(bindings[0].ConsumedProperty);
Assert.Empty(bindings[0].ConsumedProperties);
}
private class BlogWithEntityType : Blog

Просмотреть файл

@ -702,7 +702,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
private ModelBuilder CreateModelBuilder()
{
var contextServices = InMemoryTestHelpers.Instance.CreateContextServices();
return new ModelBuilder(contextServices.GetRequiredService<IConventionSetBuilder>()
return new ModelBuilder(new CompositeConventionSetBuilder(contextServices.GetRequiredService<IEnumerable<IConventionSetBuilder>>().ToList())
.AddConventions(new CoreConventionSetBuilder(
contextServices.GetRequiredService<CoreConventionSetBuilderDependencies>().With(CreateLogger()))
.CreateConventionSet()));

Просмотреть файл

@ -57,7 +57,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
entityType[CoreAnnotationNames.ConstructorBinding]
= new FactoryMethodConstructorBinding(
typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod("Factory"),
typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod(nameof(SomeEntity.Factory)),
new List<ParameterBinding>
{
new PropertyParameterBinding(entityType.FindProperty(nameof(SomeEntity.Id))),
@ -82,6 +82,42 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
Assert.False(entity.GooSetterCalled);
}
[Fact]
public void Can_create_materializer_for_entity_with_factory_method_with_object_array()
{
var entityType = CreateEntityType();
entityType[CoreAnnotationNames.ConstructorBinding]
= new FactoryMethodConstructorBinding(
typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod(nameof(SomeEntity.GeneralFactory)),
new List<ParameterBinding>
{
new ObjectArrayParameterBinding(
new List<ParameterBinding>
{
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_instance_factory_method()
{
@ -253,6 +289,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
public static SomeEntity Factory(int id, Guid? goo)
=> new SomeEntity(id, goo) { FactoryUsed = true };
public static SomeEntity GeneralFactory(object[] constructorArguments)
{
Assert.Equal(2, constructorArguments.Length);
return Factory((int)constructorArguments[0], (Guid?)constructorArguments[1]);
}
public bool FactoryUsed { get; set; }
public bool ParameterizedConstructorUsed { get; set; }
public bool IdSetterCalled { get; set; }

Просмотреть файл

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -144,7 +145,8 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding
var contextServices = testHelpers.CreateContextServices();
ModelBuilder = new ModelBuilder(contextServices.GetRequiredService<IConventionSetBuilder>().AddConventions(new CoreConventionSetBuilder(
ModelBuilder = new ModelBuilder(new CompositeConventionSetBuilder(contextServices.GetRequiredService<IEnumerable<IConventionSetBuilder>>().ToList())
.AddConventions(new CoreConventionSetBuilder(
contextServices.GetRequiredService<CoreConventionSetBuilderDependencies>().With(modelLogger))
.CreateConventionSet()));