Moved mapping, and abstractions for migrations

This commit is contained in:
Bjarke Berg
2019-11-05 10:19:13 +01:00
parent e37fdea574
commit b4e98286f5
13 changed files with 1 additions and 13 deletions

View File

@@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a base class for collection builders.
/// </summary>
/// <typeparam name="TBuilder">The type of the builder.</typeparam>
/// <typeparam name="TCollection">The type of the collection.</typeparam>
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class CollectionBuilderBase<TBuilder, TCollection, TItem> : ICollectionBuilder<TCollection, TItem>
where TBuilder: CollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : class, IBuilderCollection<TItem>
{
private readonly List<Type> _types = new List<Type>();
private readonly object _locker = new object();
private Type[] _registeredTypes;
/// <summary>
/// Gets the internal list of types as an IEnumerable (immutable).
/// </summary>
public IEnumerable<Type> GetTypes() => _types;
/// <inheritdoc />
public virtual void RegisterWith(IRegister register)
{
if (_registeredTypes != null)
throw new InvalidOperationException("This builder has already been registered.");
// register the collection
register.Register(CreateCollection, CollectionLifetime);
// register the types
RegisterTypes(register);
}
/// <summary>
/// Gets the collection lifetime.
/// </summary>
protected virtual Lifetime CollectionLifetime => Lifetime.Singleton;
/// <summary>
/// Configures the internal list of types.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <remarks>Throws if the types have already been registered.</remarks>
protected void Configure(Action<List<Type>> action)
{
lock (_locker)
{
if (_registeredTypes != null)
throw new InvalidOperationException("Cannot configure a collection builder after it has been registered.");
action(_types);
}
}
/// <summary>
/// Gets the types.
/// </summary>
/// <param name="types">The internal list of types.</param>
/// <returns>The list of types to register.</returns>
/// <remarks>Used by implementations to add types to the internal list, sort the list, etc.</remarks>
protected virtual IEnumerable<Type> GetRegisteringTypes(IEnumerable<Type> types)
{
return types;
}
private void RegisterTypes(IRegister register)
{
lock (_locker)
{
if (_registeredTypes != null) return;
var types = GetRegisteringTypes(_types).ToArray();
// ensure they are safe
foreach (var type in types)
EnsureType(type, "register");
// register them
foreach (var type in types)
register.Register(type);
_registeredTypes = types;
}
}
/// <summary>
/// Creates the collection items.
/// </summary>
/// <returns>The collection items.</returns>
protected virtual IEnumerable<TItem> CreateItems(IFactory factory)
{
if (_registeredTypes == null)
throw new InvalidOperationException("Cannot create items before the collection builder has been registered.");
return _registeredTypes // respect order
.Select(x => CreateItem(factory, x))
.ToArray(); // safe
}
/// <summary>
/// Creates a collection item.
/// </summary>
protected virtual TItem CreateItem(IFactory factory, Type itemType)
=> (TItem) factory.GetInstance(itemType);
/// <summary>
/// Creates a collection.
/// </summary>
/// <returns>A collection.</returns>
/// <remarks>Creates a new collection each time it is invoked.</remarks>
public virtual TCollection CreateCollection(IFactory factory)
{
return factory.CreateInstance<TCollection>(CreateItems(factory));
}
protected Type EnsureType(Type type, string action)
{
if (typeof(TItem).IsAssignableFrom(type) == false)
throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}.");
return type;
}
/// <summary>
/// Gets a value indicating whether the collection contains a type.
/// </summary>
/// <typeparam name="T">The type to look for.</typeparam>
/// <returns>A value indicating whether the collection contains the type.</returns>
/// <remarks>Some builder implementations may use this to expose a public Has{T}() method,
/// when it makes sense. Probably does not make sense for lazy builders, for example.</remarks>
public virtual bool Has<T>()
where T : TItem
{
return _types.Contains(typeof (T));
}
/// <summary>
/// Gets a value indicating whether the collection contains a type.
/// </summary>
/// <param name="type">The type to look for.</param>
/// <returns>A value indicating whether the collection contains the type.</returns>
/// <remarks>Some builder implementations may use this to expose a public Has{T}() method,
/// when it makes sense. Probably does not make sense for lazy builders, for example.</remarks>
public virtual bool Has(Type type)
{
EnsureType(type, "find");
return _types.Contains(type);
}
}
}

View File

@@ -1,171 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Implements an un-ordered collection builder.
/// </summary>
/// <typeparam name="TBuilder">The type of the builder.</typeparam>
/// <typeparam name="TCollection">The type of the collection.</typeparam>
/// <typeparam name="TItem">The type of the items.</typeparam>
/// <remarks>
/// <para>A set collection builder is the most basic collection builder,
/// where items are not ordered.</para>
/// </remarks>
public abstract class SetCollectionBuilderBase<TBuilder, TCollection, TItem> : CollectionBuilderBase<TBuilder, TCollection, TItem>
where TBuilder : SetCollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : class, IBuilderCollection<TItem>
{
protected abstract TBuilder This { get; }
/// <summary>
/// Clears all types in the collection.
/// </summary>
/// <returns>The builder.</returns>
public TBuilder Clear()
{
Configure(types => types.Clear());
return This;
}
/// <summary>
/// Adds a type to the collection.
/// </summary>
/// <typeparam name="T">The type to append.</typeparam>
/// <returns>The builder.</returns>
public TBuilder Add<T>()
where T : TItem
{
Configure(types =>
{
var type = typeof(T);
if (types.Contains(type)) types.Remove(type);
types.Add(type);
});
return This;
}
/// <summary>
/// Adds a type to the collection.
/// </summary>
/// <param name="type">The type to append.</param>
/// <returns>The builder.</returns>
public TBuilder Add(Type type)
{
Configure(types =>
{
EnsureType(type, "register");
if (types.Contains(type)) types.Remove(type);
types.Add(type);
});
return This;
}
/// <summary>
/// Adds types to the collections.
/// </summary>
/// <param name="types">The types to append.</param>
/// <returns>The builder.</returns>
public TBuilder Add(IEnumerable<Type> types)
{
Configure(list =>
{
foreach (var type in types)
{
// would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast
EnsureType(type, "register");
if (list.Contains(type)) list.Remove(type);
list.Add(type);
}
});
return This;
}
/// <summary>
/// Removes a type from the collection.
/// </summary>
/// <typeparam name="T">The type to remove.</typeparam>
/// <returns>The builder.</returns>
public TBuilder Remove<T>()
where T : TItem
{
Configure(types =>
{
var type = typeof(T);
if (types.Contains(type)) types.Remove(type);
});
return This;
}
/// <summary>
/// Removes a type from the collection.
/// </summary>
/// <param name="type">The type to remove.</param>
/// <returns>The builder.</returns>
public TBuilder Remove(Type type)
{
Configure(types =>
{
EnsureType(type, "remove");
if (types.Contains(type)) types.Remove(type);
});
return This;
}
/// <summary>
/// Replaces a type in the collection.
/// </summary>
/// <typeparam name="TReplaced">The type to replace.</typeparam>
/// <typeparam name="T">The type to insert.</typeparam>
/// <returns>The builder.</returns>
/// <remarks>Throws if the type to replace does not already belong to the collection.</remarks>
public TBuilder Replace<TReplaced, T>()
where TReplaced : TItem
where T : TItem
{
Configure(types =>
{
var typeReplaced = typeof(TReplaced);
var type = typeof(T);
if (typeReplaced == type) return;
var index = types.IndexOf(typeReplaced);
if (index < 0) throw new InvalidOperationException();
if (types.Contains(type)) types.Remove(type);
index = types.IndexOf(typeReplaced); // in case removing type changed index
types.Insert(index, type);
types.Remove(typeReplaced);
});
return This;
}
/// <summary>
/// Replaces a type in the collection.
/// </summary>
/// <param name="typeReplaced">The type to replace.</param>
/// <param name="type">The type to insert.</param>
/// <returns>The builder.</returns>
/// <remarks>Throws if the type to replace does not already belong to the collection.</remarks>
public TBuilder Replace(Type typeReplaced, Type type)
{
Configure(types =>
{
EnsureType(typeReplaced, "find");
EnsureType(type, "register");
if (typeReplaced == type) return;
var index = types.IndexOf(typeReplaced);
if (index < 0) throw new InvalidOperationException();
if (types.Contains(type)) types.Remove(type);
index = types.IndexOf(typeReplaced); // in case removing type changed index
types.Insert(index, type);
types.Remove(typeReplaced);
});
return This;
}
}
}

View File

@@ -1,91 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Composing;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="IFactory"/> class.
/// </summary>
public static class FactoryExtensions
{
/// <summary>
/// Gets an instance of a service.
/// </summary>
/// <typeparam name="T">The type of the service.</typeparam>
/// <param name="factory">The factory.</param>
/// <returns>An instance of the specified type.</returns>
/// <remarks>Throws an exception if the factory failed to get an instance of the specified type.</remarks>
public static T GetInstance<T>(this IFactory factory)
where T : class
=> (T)factory.GetInstance(typeof(T));
/// <summary>
/// Tries to get an instance of a service.
/// </summary>
/// <typeparam name="T">The type of the service.</typeparam>
/// <returns>An instance of the specified type, or null.</returns>
/// <remarks>Returns null if the factory does not know how to get an instance
/// of the specified type. Throws an exception if the factory does know how
/// to get an instance of the specified type, but failed to do so.</remarks>
public static T TryGetInstance<T>(this IFactory factory)
where T : class
=> (T)factory.TryGetInstance(typeof(T));
/// <summary>
/// Creates an instance with arguments.
/// </summary>
/// <typeparam name="T">The type of the instance.</typeparam>
/// <param name="factory">The factory.</param>
/// <param name="args">Arguments.</param>
/// <returns>An instance of the specified type.</returns>
/// <remarks>
/// <para>Throws an exception if the factory failed to get an instance of the specified type.</para>
/// <para>The arguments are used as dependencies by the factory.</para>
/// </remarks>
public static T CreateInstance<T>(this IFactory factory, params object[] args)
where T : class
=> (T)factory.CreateInstance(typeof(T), args);
/// <summary>
/// Creates an instance of a service, with arguments.
/// </summary>
/// <param name="factory"></param>
/// <param name="type">The type of the instance.</param>
/// <param name="args">Named arguments.</param>
/// <returns>An instance of the specified type.</returns>
/// <remarks>
/// <para>The instance type does not need to be registered into the factory.</para>
/// <para>The arguments are used as dependencies by the factory. Other dependencies
/// are retrieved from the factory.</para>
/// </remarks>
public static object CreateInstance(this IFactory factory, Type type, params object[] args)
{
// LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities
// including the most annoying one, which is that it does not work on singletons (hard to fix)
//return factory.GetInstance(type, args);
// this method is essentially used to build singleton instances, so it is assumed that it would be
// more expensive to build and cache a dynamic method ctor than to simply invoke the ctor, as we do
// here - this can be discussed
// TODO: we currently try the ctor with most parameters, but we could want to fall back to others
var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).OrderByDescending(x => x.GetParameters().Length).FirstOrDefault();
if (ctor == null) throw new InvalidOperationException($"Could not find a public constructor for type {type.FullName}.");
var ctorParameters = ctor.GetParameters();
var ctorArgs = new object[ctorParameters.Length];
var i = 0;
foreach (var parameter in ctorParameters)
{
// no! IsInstanceOfType is not ok here
// ReSharper disable once UseMethodIsInstanceOfType
var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType);
}
return ctor.Invoke(ctorArgs);
}
}
}

View File

@@ -1,13 +0,0 @@
namespace Umbraco.Core.Mapping
{
/// <summary>
/// Defines maps for <see cref="UmbracoMapper"/>.
/// </summary>
public interface IMapDefinition
{
/// <summary>
/// Defines maps.
/// </summary>
void DefineMaps(UmbracoMapper mapper);
}
}

View File

@@ -1,12 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Mapping
{
public class MapDefinitionCollection : BuilderCollectionBase<IMapDefinition>
{
public MapDefinitionCollection(IEnumerable<IMapDefinition> items)
: base(items)
{ }
}
}

View File

@@ -1,11 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.Mapping
{
public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase<MapDefinitionCollectionBuilder, MapDefinitionCollection, IMapDefinition>
{
protected override MapDefinitionCollectionBuilder This => this;
protected override Lifetime CollectionLifetime => Lifetime.Transient;
}
}

View File

@@ -1,130 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Mapping
{
/// <summary>
/// Represents a mapper context.
/// </summary>
public class MapperContext
{
private readonly UmbracoMapper _mapper;
private IDictionary<string, object> _items;
/// <summary>
/// Initializes a new instance of the <see cref="MapperContext"/> class.
/// </summary>
public MapperContext(UmbracoMapper mapper)
{
_mapper = mapper;
}
/// <summary>
/// Gets a value indicating whether the context has items.
/// </summary>
public bool HasItems => _items != null;
/// <summary>
/// Gets the context items.
/// </summary>
public IDictionary<string, object> Items => _items ?? (_items = new Dictionary<string, object>());
#region Map
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source)
=> _mapper.Map<TTarget>(source, this);
// let's say this is a bad (dangerous) idea, and leave it out for now
/*
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source, Action<MapperContext> f)
{
f(this);
return _mapper.Map<TTarget>(source, this);
}
*/
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source)
=> _mapper.Map<TSource, TTarget>(source, this);
// let's say this is a bad (dangerous) idea, and leave it out for now
/*
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, Action<MapperContext> f)
{
f(this);
return _mapper.Map<TSource, TTarget>(source, this);
}
*/
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target)
=> _mapper.Map(source, target, this);
// let's say this is a bad (dangerous) idea, and leave it out for now
/*
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target, Action<MapperContext> f)
{
f(this);
return _mapper.Map(source, target, this);
}
*/
/// <summary>
/// Maps an enumerable of source objects to a new list of target objects.
/// </summary>
/// <typeparam name="TSourceElement">The type of the source objects.</typeparam>
/// <typeparam name="TTargetElement">The type of the target objects.</typeparam>
/// <param name="source">The source objects.</param>
/// <returns>A list containing the target objects.</returns>
public List<TTargetElement> MapEnumerable<TSourceElement, TTargetElement>(IEnumerable<TSourceElement> source)
{
return source.Select(Map<TSourceElement, TTargetElement>).ToList();
}
#endregion
}
}

View File

@@ -1,455 +0,0 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Mapping
{
// notes:
// AutoMapper maps null to empty arrays, lists, etc
// TODO:
// when mapping from TSource, and no map is found, consider the actual source.GetType()?
// when mapping to TTarget, and no map is found, consider the actual target.GetType()?
// not sure we want to add magic to this simple mapper class, though
/// <summary>
/// Umbraco Mapper.
/// </summary>
/// <remarks>
/// <para>When a map is defined from TSource to TTarget, the mapper automatically knows how to map
/// from IEnumerable{TSource} to IEnumerable{TTarget} (using a List{TTarget}) and to TTarget[].</para>
/// <para>When a map is defined from TSource to TTarget, the mapper automatically uses that map
/// for any source type that inherits from, or implements, TSource.</para>
/// <para>When a map is defined from TSource to TTarget, the mapper can map to TTarget exclusively
/// and cannot re-use that map for types that would inherit from, or implement, TTarget.</para>
/// <para>When using the Map{TSource, TTarget}(TSource source, ...) overloads, TSource is explicit. When
/// using the Map{TTarget}(object source, ...) TSource is defined as source.GetType().</para>
/// <para>In both cases, TTarget is explicit and not typeof(target).</para>
/// </remarks>
public class UmbracoMapper
{
// note
//
// the outer dictionary *can* be modified, see GetCtor and GetMap, hence have to be ConcurrentDictionary
// the inner dictionaries are never modified and therefore can be simple Dictionary
private readonly ConcurrentDictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>> _ctors
= new ConcurrentDictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>>();
private readonly ConcurrentDictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>> _maps
= new ConcurrentDictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>>();
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoMapper"/> class.
/// </summary>
/// <param name="profiles"></param>
public UmbracoMapper(MapDefinitionCollection profiles)
{
foreach (var profile in profiles)
profile.DefineMaps(this);
}
#region Define
private static TTarget ThrowCtor<TSource, TTarget>(TSource source, MapperContext context)
=> throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances.");
private static void Identity<TSource, TTarget>(TSource source, TTarget target, MapperContext context)
{ }
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
public void Define<TSource, TTarget>()
=> Define<TSource, TTarget>(ThrowCtor<TSource, TTarget>, Identity);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="map">A mapping method.</param>
public void Define<TSource, TTarget>(Action<TSource, TTarget, MapperContext> map)
=> Define(ThrowCtor<TSource, TTarget>, map);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="ctor">A constructor method.</param>
public void Define<TSource, TTarget>(Func<TSource, MapperContext, TTarget> ctor)
=> Define(ctor, Identity);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="ctor">A constructor method.</param>
/// <param name="map">A mapping method.</param>
public void Define<TSource, TTarget>(Func<TSource, MapperContext, TTarget> ctor, Action<TSource, TTarget, MapperContext> map)
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var sourceCtors = DefineCtors(sourceType);
if (ctor != null)
sourceCtors[targetType] = (source, context) => ctor((TSource)source, context);
var sourceMaps = DefineMaps(sourceType);
sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context);
}
private Dictionary<Type, Func<object, MapperContext, object>> DefineCtors(Type sourceType)
{
return _ctors.GetOrAdd(sourceType, _ => new Dictionary<Type, Func<object, MapperContext, object>>());
}
private Dictionary<Type, Action<object, object, MapperContext>> DefineMaps(Type sourceType)
{
return _maps.GetOrAdd(sourceType, _ => new Dictionary<Type, Action<object, object, MapperContext>>());
}
#endregion
#region Map
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source)
=> Map<TTarget>(source, new MapperContext(this));
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return Map<TTarget>(source, context);
}
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source, MapperContext context)
=> Map<TTarget>(source, source?.GetType(), context);
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source)
=> Map<TSource, TTarget>(source, new MapperContext(this));
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return Map<TSource, TTarget>(source, context);
}
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, MapperContext context)
=> Map<TTarget>(source, typeof(TSource), context);
private TTarget Map<TTarget>(object source, Type sourceType, MapperContext context)
{
if (source == null)
return default;
var targetType = typeof(TTarget);
var ctor = GetCtor(sourceType, targetType);
var map = GetMap(sourceType, targetType);
// if there is a direct constructor, map
if (ctor != null && map != null)
{
var target = ctor(source, context);
map(source, target, context);
return (TTarget)target;
}
// otherwise, see if we can deal with enumerable
var ienumerableOfT = typeof(IEnumerable<>);
bool IsIEnumerableOfT(Type type) =>
type.IsGenericType &&
type.GenericTypeArguments.Length == 1 &&
type.GetGenericTypeDefinition() == ienumerableOfT;
// try to get source as an IEnumerable<T>
var sourceIEnumerable = IsIEnumerableOfT(sourceType) ? sourceType : sourceType.GetInterfaces().FirstOrDefault(IsIEnumerableOfT);
// if source is an IEnumerable<T> and target is T[] or IEnumerable<T>, we can create a map
if (sourceIEnumerable != null && IsEnumerableOrArrayOfType(targetType))
{
var sourceGenericArg = sourceIEnumerable.GenericTypeArguments[0];
var targetGenericArg = GetEnumerableOrArrayTypeArgument(targetType);
ctor = GetCtor(sourceGenericArg, targetGenericArg);
map = GetMap(sourceGenericArg, targetGenericArg);
// if there is a constructor for the underlying type, create & invoke the map
if (ctor != null && map != null)
{
// register (for next time) and do it now (for this time)
object NCtor(object s, MapperContext c) => MapEnumerableInternal<TTarget>((IEnumerable)s, targetGenericArg, ctor, map, c);
DefineCtors(sourceType)[targetType] = NCtor;
DefineMaps(sourceType)[targetType] = Identity;
return (TTarget)NCtor(source, context);
}
throw new InvalidOperationException($"Don't know how to map {sourceGenericArg.FullName} to {targetGenericArg.FullName}, so don't know how to map {sourceType.FullName} to {targetType.FullName}.");
}
throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
}
private TTarget MapEnumerableInternal<TTarget>(IEnumerable source, Type targetGenericArg, Func<object, MapperContext, object> ctor, Action<object, object, MapperContext> map, MapperContext context)
{
var targetList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericArg));
foreach (var sourceItem in source)
{
var targetItem = ctor(sourceItem, context);
map(sourceItem, targetItem, context);
targetList.Add(targetItem);
}
object target = targetList;
if (typeof(TTarget).IsArray)
{
var elementType = typeof(TTarget).GetElementType();
if (elementType == null) throw new PanicException("elementType == null which should never occur");
var targetArray = Array.CreateInstance(elementType, targetList.Count);
targetList.CopyTo(targetArray, 0);
target = targetArray;
}
return (TTarget)target;
}
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target)
=> Map(source, target, new MapperContext(this));
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return Map(source, target, context);
}
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target, MapperContext context)
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var map = GetMap(sourceType, targetType);
// if there is a direct map, map
if (map != null)
{
map(source, target, context);
return target;
}
// we cannot really map to an existing enumerable - give up
throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}.");
}
private Func<object, MapperContext, object> GetCtor(Type sourceType, Type targetType)
{
if (_ctors.TryGetValue(sourceType, out var sourceCtor) && sourceCtor.TryGetValue(targetType, out var ctor))
return ctor;
// we *may* run this more than once but it does not matter
ctor = null;
foreach (var (stype, sctors) in _ctors)
{
if (!stype.IsAssignableFrom(sourceType)) continue;
if (!sctors.TryGetValue(targetType, out ctor)) continue;
sourceCtor = sctors;
break;
}
if (ctor == null) return null;
_ctors.AddOrUpdate(sourceType, sourceCtor, (k, v) =>
{
// Add missing constructors
foreach (var c in sourceCtor)
{
if (!v.ContainsKey(c.Key))
{
v.Add(c.Key, c.Value);
}
}
return v;
});
return ctor;
}
private Action<object, object, MapperContext> GetMap(Type sourceType, Type targetType)
{
if (_maps.TryGetValue(sourceType, out var sourceMap) && sourceMap.TryGetValue(targetType, out var map))
return map;
// we *may* run this more than once but it does not matter
map = null;
foreach (var (stype, smap) in _maps)
{
if (!stype.IsAssignableFrom(sourceType)) continue;
// TODO: consider looking for assignable types for target too?
if (!smap.TryGetValue(targetType, out map)) continue;
sourceMap = smap;
break;
}
if (map == null) return null;
if (_maps.ContainsKey(sourceType))
{
foreach (var m in sourceMap)
{
if (!_maps[sourceType].TryGetValue(m.Key, out _))
_maps[sourceType].Add(m.Key, m.Value);
}
}
else
_maps[sourceType] = sourceMap;
return map;
}
private static bool IsEnumerableOrArrayOfType(Type type)
{
if (type.IsArray && type.GetArrayRank() == 1) return true;
if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true;
return false;
}
private static Type GetEnumerableOrArrayTypeArgument(Type type)
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
throw new PanicException($"Could not get enumerable or array type from {type}");
}
/// <summary>
/// Maps an enumerable of source objects to a new list of target objects.
/// </summary>
/// <typeparam name="TSourceElement">The type of the source objects.</typeparam>
/// <typeparam name="TTargetElement">The type of the target objects.</typeparam>
/// <param name="source">The source objects.</param>
/// <returns>A list containing the target objects.</returns>
public List<TTargetElement> MapEnumerable<TSourceElement, TTargetElement>(IEnumerable<TSourceElement> source)
{
return source.Select(Map<TSourceElement, TTargetElement>).ToList();
}
/// <summary>
/// Maps an enumerable of source objects to a new list of target objects.
/// </summary>
/// <typeparam name="TSourceElement">The type of the source objects.</typeparam>
/// <typeparam name="TTargetElement">The type of the target objects.</typeparam>
/// <param name="source">The source objects.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>A list containing the target objects.</returns>
public List<TTargetElement> MapEnumerable<TSourceElement, TTargetElement>(IEnumerable<TSourceElement> source, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return source.Select(x => Map<TSourceElement, TTargetElement>(x, context)).ToList();
}
/// <summary>
/// Maps an enumerable of source objects to a new list of target objects.
/// </summary>
/// <typeparam name="TSourceElement">The type of the source objects.</typeparam>
/// <typeparam name="TTargetElement">The type of the target objects.</typeparam>
/// <param name="source">The source objects.</param>
/// <param name="context">A mapper context.</param>
/// <returns>A list containing the target objects.</returns>
public List<TTargetElement> MapEnumerable<TSourceElement, TTargetElement>(IEnumerable<TSourceElement> source, MapperContext context)
{
return source.Select(x => Map<TSourceElement, TTargetElement>(x, context)).ToList();
}
#endregion
}
}

View File

@@ -1,22 +0,0 @@
using System;
namespace Umbraco.Core.Migrations
{
/// <summary>
/// Used if a migration has executed but the whole process has failed and cannot be rolled back
/// </summary>
internal class DataLossException : Exception
{
public DataLossException(string msg)
: base(msg)
{
}
public DataLossException(string msg, Exception inner)
: base(msg, inner)
{
}
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.Migrations
{
/// <summary>
/// Represents a migration.
/// </summary>
public interface IMigration : IDiscoverable
{
/// <summary>
/// Executes the migration.
/// </summary>
void Migrate();
}
}

View File

@@ -1,28 +0,0 @@
using System;
namespace Umbraco.Core.Migrations
{
/// <summary>
/// Represents errors that occurs when a migration exception is not executed.
/// </summary>
/// <remarks>
/// <para>Migration expression such as Alter.Table(...).Do() *must* end with Do() else they are
/// not executed. When a non-executed expression is detected, an IncompleteMigrationExpressionException
/// is thrown.</para>
/// </remarks>
public class IncompleteMigrationExpressionException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="IncompleteMigrationExpressionException"/> class.
/// </summary>
public IncompleteMigrationExpressionException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="IncompleteMigrationExpressionException"/> class with a message.
/// </summary>
public IncompleteMigrationExpressionException(string message)
: base(message)
{ }
}
}

View File

@@ -1,10 +0,0 @@
namespace Umbraco.Core.Migrations
{
public class NoopMigration : IMigration
{
public void Migrate()
{
// nop
}
}
}

View File

@@ -153,7 +153,6 @@
<Compile Include="Cache\DictionaryAppCache.cs" />
<Compile Include="Cache\TypedCacheRefresherBase.cs" />
<Compile Include="Compose\AuditEventsComposer.cs" />
<Compile Include="Composing\CollectionBuilderBase.cs" />
<Compile Include="Composing\ComponentComposer.cs" />
<Compile Include="Composing\ComposeAfterAttribute.cs" />
<Compile Include="Composing\ComposeBeforeAttribute.cs" />
@@ -161,7 +160,6 @@
<Compile Include="Composing\Composition.cs" />
<Compile Include="Composing\IComposer.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\SetCollectionBuilderBase.cs" />
<Compile Include="CompositionExtensions.cs" />
<Compile Include="Composing\ICoreComposer.cs" />
<Compile Include="Composing\IUserComposer.cs" />
@@ -214,7 +212,6 @@
<Compile Include="Events\RecycleBinEventArgs.cs" />
<Compile Include="Events\RollbackEventArgs.cs" />
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\Current.cs" />
<Compile Include="Composing\LazyCollectionBuilderBase.cs" />
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
@@ -231,7 +228,7 @@
<Compile Include="Manifest\IManifestFilter.cs" />
<Compile Include="Manifest\ManifestFilterCollection.cs" />
<Compile Include="Manifest\ManifestFilterCollectionBuilder.cs" />
<Compile Include="Mapping\MapperContext.cs" />
<Compile Include="Migrations\IMigrationBuilder.cs" />
<Compile Include="Migrations\Upgrade\Common\DeleteKeysAndIndexes.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ContentPickerPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DecimalPreValueMigrator.cs" />
@@ -262,10 +259,6 @@
<Compile Include="Migrations\Upgrade\V_8_1_0\FixContentNuCascade.cs" />
<Compile Include="Migrations\Upgrade\V_8_1_0\RenameUserLoginDtoDateIndex.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" />
<Compile Include="Mapping\MapDefinitionCollection.cs" />
<Compile Include="Mapping\MapDefinitionCollectionBuilder.cs" />
<Compile Include="Mapping\IMapDefinition.cs" />
<Compile Include="Mapping\UmbracoMapper.cs" />
<Compile Include="Migrations\Upgrade\V_8_1_0\ConvertTinyMceAndGridMediaUrlsToLocalLink.cs" />
<Compile Include="Models\ContentBaseExtensions.cs" />
<Compile Include="Models\IContent.cs" />
@@ -440,7 +433,6 @@
<Compile Include="Manifest\ManifestContentAppDefinition.cs" />
<Compile Include="Manifest\ManifestContentAppFactory.cs" />
<Compile Include="Manifest\ManifestDashboard.cs" />
<Compile Include="Migrations\IncompleteMigrationExpressionException.cs" />
<Compile Include="Migrations\MergeBuilder.cs" />
<Compile Include="Migrations\MigrationBase_Extra.cs" />
<Compile Include="Migrations\PostMigrations\IPublishedSnapshotRebuilder.cs" />
@@ -554,7 +546,6 @@
<Compile Include="Migrations\Expressions\Create\KeysAndIndexes\CreateKeysAndIndexesBuilder.cs" />
<Compile Include="Migrations\Expressions\Create\Table\CreateTableOfDtoBuilder.cs" />
<Compile Include="Migrations\Expressions\Delete\KeysAndIndexes\DeleteKeysAndIndexesBuilder.cs" />
<Compile Include="Migrations\IMigrationBuilder.cs" />
<Compile Include="Migrations\Install\DatabaseBuilder.cs" />
<Compile Include="Deploy\ArtifactBase.cs" />
<Compile Include="Deploy\ArtifactDependency.cs" />
@@ -616,7 +607,6 @@
<Compile Include="Manifest\DataEditorConverter.cs" />
<Compile Include="Migrations\MigrationBuilder.cs" />
<Compile Include="Migrations\MigrationPlan.cs" />
<Compile Include="Migrations\NoopMigration.cs" />
<Compile Include="Migrations\Upgrade\UmbracoPlan.cs" />
<Compile Include="Migrations\Upgrade\Upgrader.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DropMigrationsTable.cs" />
@@ -824,8 +814,6 @@
<Compile Include="Persistence\Mappers\UserGroupMapper.cs" />
<Compile Include="Persistence\Mappers\UserMapper.cs" />
<Compile Include="Persistence\Mappers\UserSectionMapper.cs" />
<Compile Include="Migrations\DataLossException.cs" />
<Compile Include="Migrations\IMigration.cs" />
<Compile Include="Migrations\IMigrationContext.cs" />
<Compile Include="Migrations\IMigrationExpression.cs" />
<Compile Include="Migrations\Install\DatabaseDataCreator.cs" />