Быстрая проблема с таблицей NHibernate для каждой иерархии - PullRequest
3 голосов
/ 28 августа 2009

Я использую Fluent NHibernate версия 1.0.0.579 (последняя версия на эту дату). У меня есть абстрактный класс Activity и несколько наследующих классов, например. DummyActivity. Все они используют одну и ту же таблицу Activity, и все они имеют значение дискриминатора, основанного на интегральном типе, который указывает на отображение в проекте (не FK в базе данных).

Мы построили отображение следующим образом:

public class ActivityMap : ClassMap<Activity>
    {
        public ActivityMap()
        {
            Table("Activities");
            Id(x => x.Id).Column("ID").GeneratedBy.Guid();
            Map(x => x.ActivityName).Not.Nullable().Length(50);
            HasMany(x => x.ActivityParameters)
                .KeyColumn("ActivityID")
                .AsMap<string>(idx => idx.Column("ParameterName"), elem => elem.Column("ParameterValue"))
                .Not.LazyLoad()
                .Cascade.Delete()
                .Table("ActivityParameters");

            DiscriminateSubClassesOnColumn<int>("ActivityType")
                .SubClass<DummyActivity>(1, c => { });
        }
    }

Сгенерированный файл hbm.xml:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="***.Activity, ***, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Activities">
    <id name="Id" type="System.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ID" />
      <generator class="guid" />
    </id>
    <discriminator column="ActivityType" type="Int32" insert="true" not-null="true" />
    <property name="ActivityName" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ActivityName" length="50" not-null="true" />
    </property>
    <map cascade="delete" lazy="false" name="ActivityParameters" table="ActivityParameters">
      <key>
        <column name="ActivityID" />
      </key>
      <index type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ParameterName" />
      </index>
      <element type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ParameterValue" />
      </element>
    </map>
    <subclass name="***.DummyActivity, ***, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="1" />
  </class>
</hibernate-mapping>

По моему мнению, это выглядит как действительный файл hbm.xml, идентичный по структуре с примером, приведенным в официальном справочном документе NHibernate, то есть

<class name="IPayment" table="PAYMENT">
<id name="Id" type="Int64" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="String"/>
<property name="Amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>

Мы делаем какую-то ошибку в нашем отображении? Кроме того, может кто-нибудь указать мне новую реализацию, рекомендованную Fluent (с использованием SubClass со столбцом дискриминатора, что-то вроде

public class ActivityMap : ClassMap<Activity>
    {
        public ActivityMap()
        {
            Table("Activities");
            Id(x => x.Id).Column("ID").GeneratedBy.Guid();
            Map(x => x.ActivityName).Not.Nullable().Length(50);
            HasMany(x => x.ActivityParameters)
                .KeyColumn("ActivityID")
                .AsMap<string>(idx => idx.Column("ParameterName"), elem => elem.Column("ParameterValue"))
                .Not.LazyLoad()
                .Cascade.Delete()
                .Table("ActivityParameters");

            DiscriminateSubClassesOnColumn<int>("ActivityType");
        }
    }

public class DummyActivityMap : SubClass<DummyActivity>
{
    ///discriminator value here how???
}

?)

Трассировка стека

[FormatException: Input string was not in a correct format.]
   System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal) +7469351
   System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info) +119
   NHibernate.Type.Int32Type.FromStringValue(String xml) +36
   NHibernate.Type.Int32Type.StringToObject(String xml) +10
   NHibernate.Persister.Entity.SingleTableEntityPersister..ctor(PersistentClass persistentClass, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping mapping) +7824

[MappingException: Could not format discriminator value to SQL string of entity ***.Activity]
   NHibernate.Persister.Entity.SingleTableEntityPersister..ctor(PersistentClass persistentClass, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping mapping) +8183
   NHibernate.Persister.PersisterFactory.CreateClassPersister(PersistentClass model, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping cfg) +68
   NHibernate.Impl.SessionFactoryImpl..ctor(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners) +1468
   NHibernate.Cfg.Configuration.BuildSessionFactory() +87
   FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory() in d:\Builds\FluentNH\src\FluentNHibernate\Cfg\FluentConfiguration.cs:93

[FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.

]
   ***.Container.ConfigureNHibernate() in ***.Unity\Container.cs:92
   ***.Container.ConfigureContainer() in ***.Unity\Container.cs:60
   ***.Container.GetInstance() in ***.Unity\Container.cs:45
   ***.Global.CreateContainer() in ***\Global.asax.cs:72
   ***.Global.Application_Start(Object sender, EventArgs e) in ***\Global.asax.cs:44

Ответы [ 3 ]

2 голосов
/ 28 августа 2009

Я понял это в случае перечислений.

Учитывая этот тип перечисления:

public enum ActivityType
{
    [EnumKey("1")]
    [EnumDescription("ImportFromFile")]
    ImportFromFile,
}

где EnumKey и EnumDescription - (популярные) методы расширения, я переопределяю Activity как

public abstract class Activity
    {
        public virtual Guid Id { get; set; }
        public virtual ActivityExecutionResult ExecutionResult { get; private set; }

        public virtual ActivityExecutionStatus ExecutionStatus {get;private set;}

        public abstract ActivityExecutionStatus Execute();

        public virtual string ActivityName { get; private set; }

        public virtual IDictionary<string, string> ActivityParameters { get; private set; }

        public virtual ActivityType ActivityType { get; private set; }
    }

Файл отображения выглядит следующим образом:

public class ActivityMap : ClassMap<Activity>
    {
        public ActivityMap()
        {
            Table("Activities");
            Id(x => x.Id).Column("ID").GeneratedBy.Guid();
            Map(x => x.ActivityName).Not.Nullable().Length(50);
            Map(x => x.ActivityType).CustomType<int>().Column("ActivityType").Not.Nullable();
            HasMany(x => x.ActivityParameters)
                .KeyColumn("ActivityID")
                .AsMap<string>(idx => idx.Column("ParameterName"), elem => elem.Column("ParameterValue"))
                .Not.LazyLoad()
                .Cascade.Delete()
                .Table("ActivityParameters");

            DiscriminateSubClassesOnColumn("ActivityType");
        }
    }

    public class ImportActivityFromFileMap : SubclassMap<ImportActivityFromFile>
    {
        public ImportActivityFromFileMap()
        {
            DiscriminatorValue(ActivityType.ImportFromFile.GetKey());
        }
    }

Сгенерированный файл hbm выглядит так:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="***.Activity, ***, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Activities">
    <id name="Id" type="System.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ID" />
      <generator class="guid" />
    </id>
    <discriminator column="ActivityType" type="String" insert="true" not-null="true" />
    <property name="ActivityName" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ActivityName" length="50" not-null="true" />
    </property>
    <property name="ActivityType" type="Int32">
      <column name="ActivityType" not-null="true" />
    </property>
    <map cascade="delete" lazy="false" name="ActivityParameters" table="ActivityParameters">
      <key>
        <column name="ActivityID" />
      </key>
      <index type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ParameterName" />
      </index>
      <element type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ParameterValue" />
      </element>
    </map>
    <subclass name="***.ImportActivityFromFile, ***, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="1" />
  </class>
</hibernate-mapping>

Работает как шарм!

0 голосов
/ 17 сентября 2009

Я покажу другую реализацию, которую я придумал.

public class SmartEnumMapping<T> : IUserType
    {
        #region IUserType Members

        public object Assemble(object cached, object owner)
        {
            return cached;
        }

        public object DeepCopy(object value)
        {
            return value;
        }

        public object Disassemble(object value)
        {
            return value;
        }

        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }

        public bool IsMutable
        {
            get { return false; }
        }

        public new bool Equals(object x, object y)
        {
            return object.Equals(x, y);
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            int index0 = rs.GetOrdinal(names[0]);
            if (rs.IsDBNull(index0))
            {
                return null;
            }
            string key = rs.GetString(index0);
            return EnumExtensions.EnumParseKey<T>(key, false, true);
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            if (value == null)
            {
                ((IDbDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
            }
            else
            {
                T enumValue = (T)Enum.Parse(typeof(T), value.ToString());
                ((IDbDataParameter)cmd.Parameters[index]).Value = enumValue.GetKey();
            }
        }

        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        public Type ReturnedType
        {
            get { return typeof(T); }
        }

        public global::NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get { return new SqlType[] { SqlTypeFactory.GetString(4096) }; }
        }

        #endregion
}

При этом отображение становится

Map(x => x.ActivityType).CustomType<SmartEnumMapping<ActivityType>>().Column("ActivityType").Not.Nullable();

ActivityType выглядит как

public enum ActivityType
    {
        [EnumKey("1")]
        [EnumDescription("dada")]
        dad,

        [EnumKey("2")]
        [EnumDescription("da")]
        ImportCalculAtasateSfarsitLuna,

        [EnumKey("3")]
        [EnumDescription("da")]
        das,

        }

При этом в коде я могу использовать «ActivityType.das», но при сохранении он сохраняется «3». Опять же, при чтении из базы данных я читаю «3», но я преобразую это в «ActivityType.das».

Полагаю, это более подходящий ответ, объясняющий поведение, упомянутое в предыдущем ответе.

Еще раз: EnumKey, EnumDescription, EnumParseKey и т. Д. - это то, что можно легко найти в Интернете.

0 голосов
/ 28 августа 2009

Вы выяснили, как использовать перечисления вместо целых чисел? С целыми числами это работает как вы описали, но с перечислениями это не так. Даже если я приведу их к int

например. DiscriminatorValue ((INT) SomeEnum.SomeVaue))

...