是否可以将数据库列映射到常量值而无需实体类中的属性?这基本上是一种解决方法,用于在数据库中该列上缺少缺省值,并结合NOT NULL约束。数据库是外部的,无法更改,但我不需要该表中的所有列,因此不希望在我的实体类中具有相应的属性。
我的要求与this Hibernate JIRA issue中描述的基本相同。
答案 0 :(得分:2)
根据Firos的回答我解决了这个问题。但是,我不太喜欢使用的语法以及我必须为每个实体的默认值创建一个新类的事实。
我现在的语法如下:
mapping.ConstantValue(0).Column(@"client_id");
// or
mapping.ConstantValue(0, @"client_id");
我为它创建了以下扩展方法:
public static PropertyPart
ConstantValue<TType, TValue>(this ClasslikeMapBase<TType> map, TValue value)
{
var getter =
new ConstantValueGetter<TValue>(CreateUniqueMemberName(), value);
ConstantValueAccessor.RegisterGetter(typeof(TType), getter);
var propertyInfo =
new GetterSetterPropertyInfo(typeof(TType), typeof(TValue),
getter.PropertyName, getter.Method, null);
var parameter = Expression.Parameter(typeof(TType), "x");
Expression body = Expression.Property(parameter, propertyInfo);
body = Expression.Convert(body, , typeof(object));
var lambda = Expression.Lambda<Func<TType, object>>(body, parameter);
return map.Map(lambda).Access.Using<ConstantValueAccessor>();
}
public static PropertyPart
ConstantValue<TType, TValue>(this ClasslikeMapBase<TType> map,
TValue value, string column)
{
return map.ConstantValue(value).Column(column);
}
重要的区别是:
这些扩展方法中的第一个返回PropertyPart
,必须与Column
方法一起使用,以指定常量值应映射到哪一列。因此,在执行扩展方法时,列名称是未知的,我们需要自己创建一个。这是由CreateUniqueMemberName
:
private static string CreateUniqueMemberName()
{
return "Dummy" + Guid.NewGuid().ToString("N");
}
因为您只能将类型指定为访问策略而不是实例,所以我无法创建IPropertyAccessor
实现,只允许我在构造函数中传递IGetter
实例。这就是ConstantValueAccessor.RegisterGetter(typeof(TType), getter);
解决的问题。 ConstantValueAccessor
有一个静态的getter集合:
internal class ConstantValueAccessor : IPropertyAccessor
{
private static readonly
ConcurrentDictionary<Type, SynchronizedCollection<IGetter>> _getters =
new ConcurrentDictionary<Type, SynchronizedCollection<IGetter>>();
public static void RegisterGetter(Type type, IGetter getter)
{
var getters =
_getters.GetOrAdd(type,
t => new SynchronizedCollection<IGetter>());
getters.Add(getter);
}
public IGetter GetGetter(Type theClass, string propertyName)
{
SynchronizedCollection<IGetter> getters;
if (!_getters.TryGetValue(theClass, out getters))
return null;
return getters.SingleOrDefault(x => x.PropertyName == propertyName);
}
// ...
}
ConstantValueGetter<T>
的实施与提供的链接相同。
因为实施GetterSetterPropertyInfo
并不是那么有趣,here就是这样。一个重要的区别是,这个实现与(Fluent)NHibernate没有任何依赖关系。
答案 1 :(得分:1)
如果您不想在实体类中引入属性,我看到的唯一解决方案是创建自定义属性访问器,它始终返回常量值。这可能是实施:
public class ConstantAccessor : IPropertyAccessor
{
#region IPropertyAccessor Members
public IGetter GetGetter(Type theClass, string propertyName)
{
return new ConstantGetter();
}
public ISetter GetSetter(Type theClass, string propertyName)
{
return new NoopSetter();
}
public bool CanAccessThroughReflectionOptimizer
{
get { return false; }
}
#endregion
[Serializable]
private class ConstantGetter : IGetter
{
#region IGetter Members
public object Get(object target)
{
return 0; // Always return constant value
}
public Type ReturnType
{
get { return typeof(object); }
}
public string PropertyName
{
get { return null; }
}
public MethodInfo Method
{
get { return null; }
}
public object GetForInsert(object owner, IDictionary mergeMap,
ISessionImplementor session)
{
return null;
}
#endregion
}
[Serializable]
private class NoopSetter : ISetter
{
#region ISetter Members
public void Set(object target, object value)
{
}
public string PropertyName
{
get { return null; }
}
public MethodInfo Method
{
get { return null; }
}
#endregion
}
}
以下是如何使用它:
<property name="Value"
access="ConsoleApplication2.ConstantAccessor, ConsoleApplication2"
column="a_value" type="int" />
属性“值”不需要存在于您的实体中。这是因为属性“名称”是必需的。
答案 2 :(得分:1)
我的实现与hival具有相同的想法,但更进一步。基础是IPropertyAccessor的实现
/// <summary>
/// Defaultvalues für nicht (mehr) benötigte Spalten siehe
/// http://elegantcode.com/2009/07/13/using-nhibernate-for-legacy-databases/
/// </summary>
public abstract class DefaultValuesBase : IPropertyAccessor
{
public abstract IEnumerable<IGetter> DefaultValueGetters { get; }
public bool CanAccessThroughReflectionOptimizer
{
get { return false; }
}
public IGetter GetGetter(Type theClass, string propertyName)
{
return DefaultValueGetters.SingleOrDefault(getter => getter.PropertyName == propertyName);
}
public ISetter GetSetter(Type theClass, string propertyName)
{
return new NoopSetter();
}
}
// taken from the link
[Serializable]
public class DefaultValueGetter<T> : IGetter {...}
// ---- and the most tricky part ----
public static void DefaultValues<T>(this ClasslikeMapBase<T> map, DefaultValuesBase defaults)
{
DefaultValuesInternal<T>(map.Map, defaults);
}
public static void DefaultValues<T>(this CompositeElementPart<T> map, DefaultValuesBase defaults)
{
DefaultValuesInternal<T>(map.Map, defaults);
}
private static void DefaultValuesInternal<T>(
Func<Expression<Func<T, object>>, PropertyPart> mapFunction, DefaultValuesBase defaults)
{
var noopSetter = new NoopSetter();
var defaultsType = defaults.GetType();
foreach (var defaultgetter in defaults.DefaultValueGetters)
{
var parameter = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Property(parameter,
new GetterSetterPropertyInfo(typeof(T), defaultgetter, noopSetter));
body = Expression.Convert(body, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(body, parameter);
mapFunction(lambda).Column(defaultgetter.PropertyName).Access.Using(defaultsType);
}
}
// GetterSetterPropertyInfo inherits PropertyInfo with important part
public override string Name
{
get { return m_getter.PropertyName; } // propertyName is the column in db
}
// and finally in SomeEntityMap
this.DefaultValues(new SomeEntityDefaults());
public class SomeEntityDefaults : DefaultValuesBase
{
public override IEnumerable<IGetter> DefaultValueGetters
{
get
{
return new [] {
new DefaultValueGetter<int>("someColumn", 1),
new DefaultValueGetter<string>("somestrColumn", "empty"),
};
}
}
}