Skip to content

Commit

Permalink
[EF7] Add composite support
Browse files Browse the repository at this point in the history
Enum support is partially done as well, but blocked by EF7 core issues.

Relates to #760
  • Loading branch information
roji committed Oct 31, 2015
1 parent 06043b9 commit a9401b1
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 43 deletions.
49 changes: 42 additions & 7 deletions src/EntityFramework7.Npgsql/Storage/Internal/NpgsqlTypeMapper.cs
Expand Up @@ -7,12 +7,15 @@
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Metadata;
using Microsoft.Data.Entity.Utilities;
using Npgsql;
using Npgsql.BackendMessages;
using Npgsql.TypeHandlers;
using Npgsql.TypeHandlers.NumericHandlers;
using NpgsqlTypes;

Expand All @@ -21,7 +24,7 @@ namespace Microsoft.Data.Entity.Storage.Internal
{
// TODO: Provider-specific types?
// TODO: BIT(1) vs. BIT(N)
// TODO: Enums? Ranges?
// TODO: Enums? Ranges? Composite?
// TODO: Arrays? But this would conflict with navigation...
public class NpgsqlTypeMapper : RelationalTypeMapper
{
Expand All @@ -32,18 +35,50 @@ public NpgsqlTypeMapper()
{
// Reflect over Npgsql's type mappings and generate EF7 type mappings from them

// Note that enums aren't supported yet, see https://github.com/aspnet/EntityFramework/issues/3620

// First, PostgreSQL type name (string) -> RelationalTypeMapping
_simpleNameMappings = TypeHandlerRegistry.HandlerTypes.Values
// Base types
.Where(tam => tam.Mapping.NpgsqlDbType.HasValue)
.ToDictionary(
tam => tam.Mapping.PgName,
tam => (RelationalTypeMapping)new NpgsqlTypeMapping(tam.Mapping.PgName, GetTypeHandlerTypeArgument(tam.HandlerType), tam.Mapping.NpgsqlDbType.Value)
);
.Select(tam => new {
Name = tam.Mapping.PgName,
Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(tam.Mapping.PgName, GetTypeHandlerTypeArgument(tam.HandlerType), tam.Mapping.NpgsqlDbType.Value)
})
// Enums
//.Concat(TypeHandlerRegistry.GlobalEnumMappings.Select(kv => new {
// Name = kv.Key,
// Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(kv.Key, ((IEnumHandler)kv.Value).EnumType)
//}))
// Composites
.Concat(TypeHandlerRegistry.GlobalCompositeMappings.Select(kv => new {
Name = kv.Key,
Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(kv.Key, ((ICompositeHandler)kv.Value).CompositeType)
}))
// Output
.ToDictionary(x => x.Name, x => x.Mapping);

// Second, CLR type -> RelationalTypeMapping
_simpleMappings = TypeHandlerRegistry.HandlerTypes.Values
// Base types
.Select(tam => tam.Mapping)
.Where(m => m.NpgsqlDbType.HasValue)
.SelectMany(m => m.Types, (m, t) => (RelationalTypeMapping)new NpgsqlTypeMapping(m.PgName, t, m.NpgsqlDbType.Value))
.ToDictionary(m => m.ClrType, m => m);
.SelectMany(m => m.Types, (m, t) => new {
Type = t,
Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(m.PgName, t, m.NpgsqlDbType.Value)
})
// Enums
//.Concat(TypeHandlerRegistry.GlobalEnumMappings.Select(kv => new {
// Type = ((IEnumHandler)kv.Value).EnumType,
// Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(kv.Key, ((IEnumHandler)kv.Value).EnumType)
//}))
// Composites
.Concat(TypeHandlerRegistry.GlobalCompositeMappings.Select(kv => new {
Type = ((ICompositeHandler)kv.Value).CompositeType,
Mapping = (RelationalTypeMapping)new NpgsqlTypeMapping(kv.Key, ((ICompositeHandler)kv.Value).CompositeType)
}))
// Output
.ToDictionary(x => x.Type, x => x.Mapping);
}

protected override string GetColumnType(IProperty property) => property.Npgsql().ColumnType;
Expand Down
Expand Up @@ -14,17 +14,24 @@ namespace Microsoft.Data.Entity.Storage.Internal
{
public class NpgsqlTypeMapping : RelationalTypeMapping
{
public new NpgsqlDbType StoreType { get; }
public new NpgsqlDbType? StoreType { get; }

internal NpgsqlTypeMapping([NotNull] string defaultTypeName, [NotNull] Type clrType, NpgsqlDbType storeType)
: base(defaultTypeName, clrType)
{
StoreType = storeType;
}

internal NpgsqlTypeMapping([NotNull] string defaultTypeName, [NotNull] Type clrType)
: base(defaultTypeName, clrType)
{ }

protected override void ConfigureParameter([NotNull] DbParameter parameter)
{
((NpgsqlParameter)parameter).NpgsqlDbType = StoreType;
if (StoreType.HasValue)
{
((NpgsqlParameter) parameter).NpgsqlDbType = StoreType.Value;
}
}
}
}
6 changes: 3 additions & 3 deletions src/Npgsql/NpgsqlConnection.cs
Expand Up @@ -1075,7 +1075,7 @@ public NpgsqlRawCopyStream BeginRawBinaryCopy(string copyCommand)
throw new ArgumentException("pgName can't be empty", nameof(pgName));
Contract.EndContractBlock();

TypeHandlerRegistry.RegisterEnumTypeGlobally<TEnum>(pgName ?? typeof(TEnum).Name.ToLower());
TypeHandlerRegistry.MapEnumTypeGlobally<TEnum>(pgName ?? typeof(TEnum).Name.ToLower());
}

internal static void UnmapEnumGlobally<TEnum>() where TEnum : struct
Expand Down Expand Up @@ -1161,7 +1161,7 @@ public void MapComposite<T>(string pgName = null) where T : new()
throw new InvalidOperationException("Connection must be open and idle to perform registration");
Contract.EndContractBlock();

Connector.TypeHandlerRegistry.RegisterCompositeType<T>(pgName ?? typeof(T).Name.ToLower());
Connector.TypeHandlerRegistry.MapCompositeType<T>(pgName ?? typeof(T).Name.ToLower());
}

/// <summary>
Expand All @@ -1186,7 +1186,7 @@ public static void MapCompositeGlobally<T>(string pgName = null) where T : new()
throw new ArgumentException("pgName can't be empty", nameof(pgName));
Contract.EndContractBlock();

TypeHandlerRegistry.RegisterCompositeTypeGlobally<T>(pgName ?? typeof(T).Name.ToLower());
TypeHandlerRegistry.MapCompositeTypeGlobally<T>(pgName ?? typeof(T).Name.ToLower());
}

// ReSharper disable once UnusedMember.Global
Expand Down
58 changes: 33 additions & 25 deletions src/Npgsql/TypeHandlerRegistry.cs
Expand Up @@ -79,8 +79,21 @@ public partial class TypeHandlerRegistry
/// </summary>
static readonly ConcurrentDictionary<string, List<BackendType>> BackendTypeCache = new ConcurrentDictionary<string, List<BackendType>>();

static ConcurrentDictionary<string, TypeHandler> _globalEnumRegistrations;
static ConcurrentDictionary<string, TypeHandler> _globalCompositeRegistrations;
static readonly ConcurrentDictionary<string, TypeHandler> _globalEnumMappings;

/// <summary>
///
/// </summary>
public static IReadOnlyDictionary<string, TypeHandler> GlobalEnumMappings
=> (IReadOnlyDictionary<string, TypeHandler>)_globalEnumMappings;

static readonly ConcurrentDictionary<string, TypeHandler> _globalCompositeMappings;

/// <summary>
///
/// </summary>
public static IReadOnlyDictionary<string, TypeHandler> GlobalCompositeMappings
=> (IReadOnlyDictionary<string, TypeHandler>)_globalCompositeMappings;

static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();

Expand Down Expand Up @@ -252,8 +265,7 @@ void RegisterTypes(List<BackendType> backendTypes)

case BackendTypeType.Enum:
TypeHandler handler;
if (_globalEnumRegistrations != null &&
_globalEnumRegistrations.TryGetValue(backendType.Name, out handler))
if (_globalEnumMappings.TryGetValue(backendType.Name, out handler))
{
ActivateEnumType(handler, backendType);
}
Expand All @@ -265,8 +277,8 @@ void RegisterTypes(List<BackendType> backendTypes)
continue;

case BackendTypeType.Composite:
if (_globalCompositeRegistrations != null &&
_globalCompositeRegistrations.TryGetValue(backendType.Name, out handler))
if (_globalCompositeMappings != null &&
_globalCompositeMappings.TryGetValue(backendType.Name, out handler))
{
ActivateCompositeType(handler, backendType);
}
Expand Down Expand Up @@ -365,7 +377,7 @@ void RegisterArrayType(BackendType backendType)
if (_arrayHandlerByType == null) {
_arrayHandlerByType = new Dictionary<Type, TypeHandler>();
}
_arrayHandlerByType[asEnumHandler.ClrType] = arrayHandler;
_arrayHandlerByType[asEnumHandler.EnumType] = arrayHandler;
return;
}

Expand All @@ -375,7 +387,7 @@ void RegisterArrayType(BackendType backendType)
if (_arrayHandlerByType == null) {
_arrayHandlerByType = new Dictionary<Type, TypeHandler>();
}
_arrayHandlerByType[asCompositeHandler.ClrType] = arrayHandler;
_arrayHandlerByType[asCompositeHandler.CompositeType] = arrayHandler;
return;
}

Expand Down Expand Up @@ -422,13 +434,9 @@ void RegisterRangeType(BackendType backendType)
ActivateEnumType(handler, backendTypeInfo);
}

internal static void RegisterEnumTypeGlobally<TEnum>(string pgName) where TEnum : struct
internal static void MapEnumTypeGlobally<TEnum>(string pgName) where TEnum : struct
{
if (_globalEnumRegistrations == null) {
_globalEnumRegistrations = new ConcurrentDictionary<string, TypeHandler>();
}

_globalEnumRegistrations[pgName] = new EnumHandler<TEnum>();
_globalEnumMappings[pgName] = new EnumHandler<TEnum>();
}

void ActivateEnumType(TypeHandler handler, BackendType backendType)
Expand All @@ -450,18 +458,18 @@ void ActivateEnumType(TypeHandler handler, BackendType backendType)

internal static void UnregisterEnumTypeGlobally<TEnum>() where TEnum : struct
{
var pgName = _globalEnumRegistrations
var pgName = _globalEnumMappings
.Single(kv => kv.Value is EnumHandler<TEnum>)
.Key;
TypeHandler _;
_globalEnumRegistrations.TryRemove(pgName, out _);
_globalEnumMappings.TryRemove(pgName, out _);
}

#endregion

#region Composite

internal void RegisterCompositeType<T>(string pgName) where T : new()
internal void MapCompositeType<T>(string pgName) where T : new()
{
var backendTypeInfo = _backendTypes.FirstOrDefault(t => t.Name == pgName);
if (backendTypeInfo == null)
Expand All @@ -473,14 +481,9 @@ internal void RegisterCompositeType<T>(string pgName) where T : new()
ActivateCompositeType(handler, backendTypeInfo);
}

internal static void RegisterCompositeTypeGlobally<T>(string pgName) where T : new()
internal static void MapCompositeTypeGlobally<T>(string pgName) where T : new()
{
if (_globalCompositeRegistrations == null)
{
_globalCompositeRegistrations = new ConcurrentDictionary<string, TypeHandler>();
}

_globalCompositeRegistrations[pgName] = new CompositeHandler<T>();
_globalCompositeMappings[pgName] = new CompositeHandler<T>();
}

void ActivateCompositeType(TypeHandler templateHandler, BackendType backendType)
Expand Down Expand Up @@ -531,7 +534,7 @@ void ActivateCompositeType(TypeHandler templateHandler, BackendType backendType)
internal static void UnregisterCompositeTypeGlobally(string pgName)
{
TypeHandler _;
_globalCompositeRegistrations.TryRemove(pgName, out _);
_globalCompositeMappings.TryRemove(pgName, out _);
}

#endregion
Expand Down Expand Up @@ -687,6 +690,8 @@ internal static void UnregisterCompositeTypeGlobally(string pgName)
"If you wish to map it to a PostgreSQL composite type you need to register it before usage, please refer to the documentation.");
}

// Only errors from here

if (type.GetTypeInfo().IsEnum) {
throw new NotSupportedException(
$"The CLR enum type {type.Name} must be registered with Npgsql before usage, please refer to the documentation.");
Expand Down Expand Up @@ -761,6 +766,9 @@ internal static DbType ToDbType(NpgsqlDbType npgsqlDbType)

static TypeHandlerRegistry()
{
_globalEnumMappings = new ConcurrentDictionary<string, TypeHandler>();
_globalCompositeMappings = new ConcurrentDictionary<string, TypeHandler>();

HandlerTypes = new Dictionary<string, TypeAndMapping>();
NpgsqlDbTypeToDbType = new Dictionary<NpgsqlDbType, DbType>();
DbTypeToNpgsqlDbType = new Dictionary<DbType, NpgsqlDbType>();
Expand Down
14 changes: 11 additions & 3 deletions src/Npgsql/TypeHandlers/CompositeHandler.cs
Expand Up @@ -33,11 +33,19 @@

namespace Npgsql.TypeHandlers
{
internal interface ICompositeHandler
/// <summary>
/// Interface implemented by all concrete handlers which handle enums
/// </summary>
public interface ICompositeHandler
{
Type ClrType { get; }
/// <summary>
/// The CLR type mapped to the PostgreSQL composite type.
/// </summary>
Type CompositeType { get; }
#pragma warning disable 1591
List<Tuple<string, uint>> RawFields { get; set; }
ICompositeHandler Clone(TypeHandlerRegistry registry);
#pragma warning restore 1591
}

/// <summary>
Expand Down Expand Up @@ -67,7 +75,7 @@ internal class CompositeHandler<T> : ChunkingTypeHandler<T>, ICompositeHandler w
object _value;
bool _wroteFieldHeader;

public Type ClrType => typeof (T);
public Type CompositeType => typeof (T);

internal CompositeHandler() {}

Expand Down
12 changes: 9 additions & 3 deletions src/Npgsql/TypeHandlers/EnumHandler.cs
Expand Up @@ -32,17 +32,23 @@

namespace Npgsql.TypeHandlers
{
internal interface IEnumHandler
/// <summary>
/// Interface implemented by all concrete handlers which handle enums
/// </summary>
public interface IEnumHandler
{
Type ClrType { get; }
/// <summary>
/// The CLR enum type mapped to the PostgreSQL enum
/// </summary>
Type EnumType { get; }
}

internal class EnumHandler<TEnum> : SimpleTypeHandler<TEnum>, IEnumHandler where TEnum : struct
{
readonly Dictionary<TEnum, string> _enumToLabel;
readonly Dictionary<string, TEnum> _labelToEnum;

public Type ClrType => typeof (TEnum);
public Type EnumType => typeof (TEnum);

public EnumHandler()
{
Expand Down
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Data.Entity.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using NpgsqlTypes;

namespace EntityFramework7.Npgsql.FunctionalTests
Expand All @@ -24,6 +25,10 @@ public BuiltInDataTypesNpgsqlFixture()
{
_testStore = NpgsqlTestStore.CreateScratch();

_testStore.ExecuteNonQuery("CREATE TYPE somecomposite AS (some_number int, some_text text)");
NpgsqlConnection.MapCompositeGlobally<SomeComposite>("somecomposite");
((NpgsqlConnection)_testStore.Connection).ReloadTypes();

_serviceProvider = new ServiceCollection()
.AddEntityFramework()
.AddNpgsql()
Expand Down Expand Up @@ -186,6 +191,9 @@ public class MappedDataTypes
// Types supported only on PostgreSQL
public PhysicalAddress Macaddr { get; set; }
public NpgsqlPoint Point { get; set; }

// Composite
public SomeComposite SomeComposite { get; set; }
}

public class MappedSizedDataTypes
Expand Down Expand Up @@ -259,5 +267,22 @@ public class MappedNullableDataTypes
// Types supported only on PostgreSQL
public PhysicalAddress Macaddr { get; set; }
public NpgsqlPoint? Point { get; set; }

// Composite
public SomeComposite SomeComposite { get; set; }
}

public class SomeComposite
{
[PgName("some_number")]
public int SomeNumber { get; set; }
[PgName("some_text")]
public string SomeText { get; set; }

public override bool Equals(object obj)
{
var o = obj as SomeComposite;
return o != null && o.SomeNumber == SomeNumber && o.SomeText == o.SomeText;
}
}
}

0 comments on commit a9401b1

Please sign in to comment.