Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Incremental steps to netnative support #3478

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/EntityFramework.Core/EntityFramework.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@
<Compile Include="..\Shared\SharedTypeExtensions.cs">
<Link>Extensions\Internal\SharedTypeExtensions.cs</Link>
</Compile>
<Compile Include="Utilities\Imply.cs" />
<Compile Include="Update\IUpdateEntry.cs" />
<Compile Include="ValueGeneration\GuidValueGenerator.cs" />
<Compile Include="ValueGeneration\HiLoValueGeneratorState.cs" />
Expand Down
16 changes: 4 additions & 12 deletions src/EntityFramework.Core/Metadata/Internal/ClrAccessorSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Internal;
Expand All @@ -12,9 +11,6 @@ namespace Microsoft.Data.Entity.Metadata.Internal
public abstract class ClrAccessorSource<TAccessor> : IClrAccessorSource<TAccessor>
where TAccessor : class
{
private static readonly MethodInfo _genericCreate
= typeof(ClrAccessorSource<TAccessor>).GetTypeInfo().GetDeclaredMethods("CreateGeneric").Single();

private readonly ThreadSafeDictionaryCache<Tuple<Type, string>, TAccessor> _cache
= new ThreadSafeDictionaryCache<Tuple<Type, string>, TAccessor>();

Expand All @@ -24,13 +20,9 @@ public virtual TAccessor GetAccessor(IPropertyBase property)
public virtual TAccessor GetAccessor(Type declaringType, string propertyName)
=> _cache.GetOrAdd(Tuple.Create(declaringType, propertyName), k => Create(k.Item1.GetAnyProperty(k.Item2)));

private TAccessor Create(PropertyInfo property)
{
var boundMethod = _genericCreate.MakeGenericMethod(property.DeclaringType, property.PropertyType, property.PropertyType.UnwrapNullableType());

return (TAccessor)boundMethod.Invoke(this, new object[] { property });
}

protected abstract TAccessor CreateGeneric<TEntity, TValue, TNonNullableEnumValue>([NotNull] PropertyInfo property) where TEntity : class;
// TODO revisit when .NET Native supports ImpliesMethodInstantiation
// original version used generics, which is much cleaner and performant but fails after ILC strips reflection info
// https://github.com/aspnet/EntityFramework/issues/3477
protected abstract TAccessor Create([NotNull] PropertyInfo property);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

using System;
using System.Reflection;
using JetBrains.Annotations;

namespace Microsoft.Data.Entity.Metadata.Internal
{
public class ClrPropertyGetterSource : ClrAccessorSource<IClrPropertyGetter>
{
protected override IClrPropertyGetter CreateGeneric<TEntity, TValue, TNonNullableEnumValue>(PropertyInfo property)
=> new ClrPropertyGetter<TEntity, TValue>(
(Func<TEntity, TValue>)property.GetMethod.CreateDelegate(typeof(Func<TEntity, TValue>)));
protected override IClrPropertyGetter Create([NotNull] PropertyInfo property)
{
var types = new[] { property.DeclaringType, property.PropertyType };
var getterType = typeof(ClrPropertyGetter<,>).MakeGenericType(types);
var funcType = typeof(Func<,>).MakeGenericType(types);
return (IClrPropertyGetter)Activator.CreateInstance(getterType, property.GetMethod.CreateDelegate(funcType));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@

using System;
using System.Reflection;
using JetBrains.Annotations;

namespace Microsoft.Data.Entity.Metadata.Internal
{
public class ClrPropertySetterSource : ClrAccessorSource<IClrPropertySetter>
{
protected override IClrPropertySetter CreateGeneric<TEntity, TValue, TNonNullableEnumValue>(PropertyInfo property)
protected override IClrPropertySetter Create([NotNull] PropertyInfo property)
{
// TODO: Handle case where there is not setter or setter is private on a base type
// Issue #753
var setterDelegate = (Action<TEntity, TValue>)property.SetMethod.CreateDelegate(typeof(Action<TEntity, TValue>));

return (property.PropertyType.IsNullableType()
&& property.PropertyType.UnwrapNullableType().GetTypeInfo().IsEnum) ?
new NullableEnumClrPropertySetter<TEntity, TValue, TNonNullableEnumValue>(setterDelegate) :
(IClrPropertySetter)new ClrPropertySetter<TEntity, TValue>(setterDelegate);
var types = new[] { property.DeclaringType, property.PropertyType };
var actionType = typeof(Action<,>).MakeGenericType(types);

Type setterType;
if (property.PropertyType.IsNullableType()
&& property.PropertyType.UnwrapNullableType().GetTypeInfo().IsEnum)
{
setterType = typeof(NullableEnumClrPropertySetter<,,>).MakeGenericType(property.DeclaringType, property.PropertyType, property.PropertyType.UnwrapNullableType());
}
else
{
setterType = typeof(ClrPropertySetter<,>).MakeGenericType(types);
}

return (IClrPropertySetter)Activator.CreateInstance(setterType, property.SetMethod.CreateDelegate(actionType));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library>
<Assembly Name="EntityFramework.Core" Activate="All" />
<!-- TODO optimize this for types that actually need reflection -->
<Assembly Name="EntityFramework.Core" Dynamic="Required All" />

<!-- TODO https://github.com/aspnet/EntityFramework/issues/3477 -->
<Type Name="Microsoft.Data.Entity.DbSet{TEntity}">
<GenericParameter Name="TEntity" Dynamic="Required All"/>
<ImpliesType Name="Microsoft.Data.Entity.Utilities.ImpliedEntityType{TEntity}" Dynamic="Required All"/>
</Type>

</Library>
</Directives>
79 changes: 79 additions & 0 deletions src/EntityFramework.Core/Utilities/Imply.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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.

#pragma warning disable 0169

using Microsoft.Data.Entity.Storage;
using System;
using Microsoft.Data.Entity.ChangeTracking.Internal;
using Microsoft.Data.Entity.Metadata.Internal;

namespace Microsoft.Data.Entity.Utilities
{
// This code exists only to trick the ILC compliation to include metadata
// about combinations of entity types and our internal types.
// This is the jumping off point for a reasoning about what generic types
// may exist at runtime.
// https://github.com/aspnet/EntityFramework/issues/3477
internal class ImpliedEntityType<TEntity>
where TEntity : class
{
InternalImplyValuesAndTypes<TEntity> EntityValueProp;

InternalImpliesTypes<TEntity> EntityProp;
InternalImpliesTypes<char> CharProp;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a way we can do this in providers since primitive type support is tied to providers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps a better method is to expose a utility class publicly and let providers pass in their primitive types.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like that. Also, I kept mulling on it after our in person discussion and here are some of my thoughts:

  • The goal is to make .NET Native support as transparent as possible to application developers (as opposed to provider writers)
  • I would like to see providers do whatever they need to do so that their valid primitive types are supported in .NET native in code that is near/related to their type mapper
  • If we do this for some well known set of types in the core it actually increases the chances that providers are just going to miss that they need to do it for all the types they support in their type mapper until they have some customer trying to use a particular type that is not part of the subset on .NET Native.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@divega I added some of the related actionable items of these in #623 (comment).

InternalImpliesTypes<short> Int16Prop;
InternalImpliesTypes<ushort> UInt16Prop;
InternalImpliesTypes<int> Int32Prop;
InternalImpliesTypes<uint> UInt32Prop;
InternalImpliesTypes<long> Int64Prop;
InternalImpliesTypes<long> UInt64Prop;
InternalImpliesTypes<double> DoubleProp;
InternalImpliesTypes<decimal> DecimalProp;
InternalImpliesTypes<float> FloatProp;
InternalImpliesTypes<string> StringProp;
InternalImpliesTypes<bool> BoolProp;
InternalImpliesTypes<byte> ByteProp;
InternalImpliesTypes<Guid> GuidProp;
InternalImpliesTypes<TimeSpan> TimeSpanProp;
InternalImpliesTypes<DateTime> DateTimeProp;
InternalImpliesTypes<DateTimeOffset> DateTimeOffsetProp;
}

internal class InternalImpliesTypes<T>
{
void CompileQuery()
=> ((IDatabase)new object()).CompileQuery<T>(null);

SimpleEntityKeyFactory<T> KeyFactoryType;
}

internal class InternalImplyTypes<TSystemType, TUserType>
where TUserType : class
{
ClrPropertyGetter<TUserType, TSystemType> ClrPropertyGetter;
ClrPropertySetter<TUserType, TSystemType> ClrPropertySetter;
}

internal class InternalImplyValuesAndTypes<TEntity>
where TEntity : class
{
InternalImplyTypes<char, TEntity> CharProp;
InternalImplyTypes<short, TEntity> Int16Prop;
InternalImplyTypes<ushort, TEntity> UInt16Prop;
InternalImplyTypes<int, TEntity> Int32Prop;
InternalImplyTypes<uint, TEntity> UInt32Prop;
InternalImplyTypes<long, TEntity> Int64Prop;
InternalImplyTypes<long, TEntity> UInt64Prop;
InternalImplyTypes<double, TEntity> DoubleProp;
InternalImplyTypes<decimal, TEntity> DecimalProp;
InternalImplyTypes<float, TEntity> FloatProp;
InternalImplyTypes<string, TEntity> StringProp;
InternalImplyTypes<bool, TEntity> BoolProp;
InternalImplyTypes<byte, TEntity> ByteProp;
InternalImplyTypes<Guid, TEntity> GuidProp;
InternalImplyTypes<TimeSpan, TEntity> TimeSpanProp;
InternalImplyTypes<DateTime, TEntity> DateTimeProp;
InternalImplyTypes<DateTimeOffset, TEntity> DateTimeOffsetProp;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library>
<Assembly Name="EntityFramework.MicrosoftSqlServer" Dynamic="Required All" />
<!-- TODO optimize this for types that actually need reflection -->
<Assembly Name="EntityFramework.MicrosoftSqlServer" Activate="All" />
</Library>
</Directives>
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library>
<!-- TODO optimize this for types that actually need reflection -->
<Assembly Name="EntityFramework.Relational" Activate="All" />
<Type Name="Microsoft.Data.Entity.Migrations.Operations.MigrationOperation" Dynamic="Required All" />
<Type Name="Microsoft.Data.Entity.Update.UpdateSqlGenerator">
<Method Name="GenerateLiteral" Signature="System.Object" Dynamic="Required" />
<Assembly Name="EntityFramework.Relational" Dynamic="Required All" />

<!-- TODO will be unnecessary after https://github.com/dotnet/corefx/pull/3917 -->
<Type Name="System.Data.Common.DbDataReader">
<MethodInstantiation Name="GetFieldValue" Arguments="System.Byte" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Int16" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.UInt16" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Int32" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.UInt32" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Int64" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.UInt64" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.String" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Single" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Double" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Decimal" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.TimeSpan" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.DateTime" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.DateTimeOffset" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Guid" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Boolean" Dynamic="Required"/>
<MethodInstantiation Name="GetFieldValue" Arguments="System.Char" Dynamic="Required"/>
</Type>
</Library>
</Directives>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library>
<!-- TODO optimize this for types that actually need reflection -->
<Assembly Name="EntityFramework.Sqlite" Activate="All" />
<Assembly Name="EntityFramework.Sqlite" Dynamic="Required All" />
</Library>
</Directives>