82 lines
3.5 KiB
C#
82 lines
3.5 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using NPoco;
|
|
using Umbraco.Core.Persistence.SqlSyntax;
|
|
using Umbraco.Core.Composing;
|
|
|
|
namespace Umbraco.Core.Persistence.Mappers
|
|
{
|
|
public abstract class BaseMapper
|
|
{
|
|
// note: using a Lazy<ISqlContext> here because during installs, we are resolving the
|
|
// mappers way before we have a configured IUmbracoDatabaseFactory, ie way before we
|
|
// have an ISqlContext - this is some nasty temporal coupling which we might want to
|
|
// cleanup eventually.
|
|
|
|
private readonly Lazy<ISqlContext> _sqlContext;
|
|
private readonly object _definedLock = new object();
|
|
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> _maps;
|
|
|
|
private ISqlSyntaxProvider _sqlSyntax;
|
|
private bool _defined;
|
|
|
|
protected BaseMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
|
|
{
|
|
_sqlContext = sqlContext;
|
|
_maps = maps;
|
|
}
|
|
|
|
protected abstract void DefineMaps();
|
|
|
|
internal string Map(string propertyName)
|
|
{
|
|
lock (_definedLock)
|
|
{
|
|
if (!_defined)
|
|
{
|
|
var sqlContext = _sqlContext.Value;
|
|
if (sqlContext == null)
|
|
throw new InvalidOperationException("Could not get an ISqlContext.");
|
|
_sqlSyntax = sqlContext.SqlSyntax;
|
|
|
|
DefineMaps();
|
|
|
|
_defined = true;
|
|
}
|
|
}
|
|
|
|
if (!_maps.TryGetValue(GetType(), out var mapperMaps))
|
|
throw new InvalidOperationException($"No maps defined for mapper {GetType().FullName}.");
|
|
if (!mapperMaps.TryGetValue(propertyName, out var mappedName))
|
|
throw new InvalidOperationException($"No map defined by mapper {GetType().FullName} for property {propertyName}.");
|
|
return mappedName;
|
|
}
|
|
|
|
protected void DefineMap<TSource, TTarget>(string sourceName, string targetName)
|
|
{
|
|
if (_sqlSyntax == null)
|
|
throw new InvalidOperationException("Do not define maps outside of DefineMaps.");
|
|
|
|
var targetType = typeof(TTarget);
|
|
|
|
// TODO ensure that sourceName is a valid sourceType property (but, slow?)
|
|
|
|
var tableNameAttribute = targetType.FirstAttribute<TableNameAttribute>();
|
|
if (tableNameAttribute == null) throw new InvalidOperationException($"Type {targetType.FullName} is not marked with a TableName attribute.");
|
|
var tableName = tableNameAttribute.Value;
|
|
|
|
// TODO maybe get all properties once and then index them
|
|
var targetProperty = targetType.GetProperty(targetName);
|
|
if (targetProperty == null) throw new InvalidOperationException($"Type {targetType.FullName} does not have a property named {targetName}.");
|
|
var columnAttribute = targetProperty.FirstAttribute<ColumnAttribute>();
|
|
if (columnAttribute == null) throw new InvalidOperationException($"Property {targetType.FullName}.{targetName} is not marked with a Column attribute.");
|
|
|
|
var columnName = columnAttribute.Name;
|
|
var columnMap = _sqlSyntax.GetQuotedTableName(tableName) + "." + _sqlSyntax.GetQuotedColumnName(columnName);
|
|
|
|
var mapperMaps = _maps.GetOrAdd(GetType(), type => new ConcurrentDictionary<string, string>());
|
|
mapperMaps[sourceName] = columnMap;
|
|
}
|
|
}
|
|
}
|