Сопоставить столбец базы данных с постоянным значением без необходимости использования свойства в классе сущности - PullRequest
4 голосов
/ 25 января 2012

Можно ли отобразить столбец базы данных на постоянное значение без необходимости использования свойства в классе сущности?По сути, это обходной путь для отсутствующего значения по умолчанию для этого столбца в базе данных в сочетании с ограничением NOT NULL.База данных является внешней и не может быть изменена, но мне не нужны все столбцы в этой таблице, и поэтому я не хочу иметь соответствующие свойства в моем классе сущностей.

Я спрашиваю в основном то же самоекак описано в этой проблеме Hibernate JIRA .

Ответы [ 3 ]

2 голосов
/ 28 января 2012

На основании ответа 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);
}

Важные отличия:

  1. Первый из этих методов расширения возвращает PropertyPart и должен использоваться вместе с методом Column, чтобы указать, какому столбцу должно быть сопоставлено постоянное значение. Из-за этого имя столбца неизвестно, когда выполняется метод расширения, и нам нужно создать его самостоятельно. Это сделано CreateUniqueMemberName:

    private static string CreateUniqueMemberName()
    {
        return "Dummy" + Guid.NewGuid().ToString("N");
    }
    
  2. Поскольку вы можете указать только тип в качестве стратегии доступа, но не экземпляр, я не смог создать реализацию IPropertyAccessor, которая позволила мне просто передать экземпляр IGetter в конструкторе. Вот что решает ConstantValueAccessor.RegisterGetter(typeof(TType), getter);. ConstantValueAccessor имеет статическую коллекцию геттеров:

    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, здесь было не очень весело это так. Одним из важных отличий является то, что эта реализация не имеет никаких зависимостей от (Свободно) NHibernate.

1 голос
/ 27 января 2012

Моя реализация использует ту же идею, что и 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"),
            };
        }
    }
}
1 голос
/ 25 января 2012

Если вы не хотите вводить свойство в свой класс сущностей, единственное решение, которое я вижу, - создать собственный метод доступа к свойствам, который всегда будет возвращать постоянное значение. Здесь возможна реализация:

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" />

Свойство «Значение» не обязательно должно существовать в вашей организации. Это здесь, потому что атрибут "имя" является обязательным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...