Вопрос по вложенным классам
От: Блудов Павел Россия  
Дата: 26.05.06 07:40
Оценка:
Игорь! разъясни, пожалуйста, генеральную политику партии в отношении вложенных классов.

Во всему выходит, что это
Автор(ы): Игорь Ткачёв
Дата: 01.07.2003
В статье подробно рассматривается состав и способы применения пространства имён Rsdn.Framework.Data, представляющего собой высокоуровневую обёртку над ADO.NET.
единственное, что с ними можно делать.

Вот такой тест совсем-совсем не работает:
public abstract class RecordHeader : EditableObject
{
    [MaxLength(50), Required]
    public abstract string Last { get; set; }
    [MaxLength(50), Required]
    public abstract string First { get; set; }
    [MaxLength(50), NullValue("")]
    public abstract string Middle { get; set; }
}

[TableName("Person")]
public abstract class Person : EditableObject
{
    [Required]                     public abstract Gender Gender     { get; set; }
    [MapIgnore(false)]
    public abstract RecordHeader Name { get; set; }
    [PrimaryKey, NonUpdatable]
    [MapField("PersonID")]         public abstract int    ID         { get; set; }
}

public abstract class PersonAccessor : DataAccessor
{
    public abstract Person SelectByName(Person p);
}

[Test]
public void SelectByNameTest()
{
    Person e = TypeAccessor.CreateInstance<Person>();
    e.Name.First = "John";
    e.Name.Last = "Pupkin";

    Person p = _da.SelectByName(e);
    Assert.AreEqual(1, p.ID);
}
Срубается из-за того, что в DbManager.CreateParameters() отсутствуют выделенные жирным строки:
public IDbDataParameter[] CreateParameters(object obj, params IDbDataParameter[] commandParameters)
        {
            ArrayList    paramList = new ArrayList();
            ObjectMapper om        = _mappingSchema.GetObjectMapper(obj.GetType());

            foreach (MemberMapper mm in om)
            {
                Type type = mm.Type;
                object value = mm.GetValue(obj);

                if (TypeHelper.IsScalar(type))
                {
                    string name  = _dataProvider.Convert(mm.Name, ConvertType.NameToParameter).ToString();

                    paramList.Add(mm.MapMemberInfo.Nullable || value == null?
                        NullParameter(name, value, mm.MapMemberInfo.NullValue):
                        Parameter    (name, value));
                }
                else
                {
                    paramList.AddRange(CreateParameters(value));
                }
            }

            if (commandParameters != null)
                foreach (IDbDataParameter p in commandParameters)
                    paramList.Add(p);

            return (IDbDataParameter[])paramList.ToArray(typeof(IDbDataParameter));
        }

Так вот, основная проблема в том, какие же имена давать таким параметрам? Вариантов два: типа Name.First и просто First.
Второе мне нравится больше, так как объекты типа:
        public abstract class RecordHeader
{
    public abstract string Name { get; set; }
}

public abstract class Person : EditableObject
{
    public abstract RecordHeader Header { get; set; }
    public abstract string Name { get; set; }
}
выглядят как попытка наступить на грабли в самом ближайшем будущем.
Кроме того, MSSQL не понимает имён параметров типа @Name.First Максимум, на что он согласен это @Name_First.

Давайте мапить поля вложенных классов без префикса, а кому очень нужно, пусть задают его явно.
Для этого нужно всего лишь избавиться от хака с GetComplexMapper.
Вместо него всегда брать и добавлять мапперов вложенных классов во внешние классы через ComplexMapper с тем же именем. Т.е. в ObjectMapper'е:

protected virtual MemberMapper CreateComplexMapper(MemberMapper mm, MemberAccessor ma)
{
    MapMemberInfo cmi = new MapMemberInfo();

    cmi.MemberAccessor = ma;
    cmi.Type = mm.Type;
    cmi.MappingSchema = mm.MappingSchema;
    cmi.Name = mm.Name;
    cmi.MemberName = mm.MemberName;

    MemberMapper mapper = new MemberMapper.ComplexMapper(mm);

    mapper.Init(cmi);

    return mapper;
}

public virtual void Init(MappingSchema mappingSchema, Type type)
{
    if (type == null) throw new ArgumentNullException("type");

    _typeAccessor  = TypeAccessor.GetAccessor(type);
    _mappingSchema = mappingSchema;
    _extension     = TypeExtension.GetTypeExtenstion(
        _typeAccessor.OriginalType, mappingSchema.Extensions);

    foreach (MemberAccessor ma in _typeAccessor)
    {
        if (GetIgnore(ma))
            continue;

        MapMemberInfo mi = new MapMemberInfo();

        mi.MemberAccessor  = ma;
        mi.Type            = ma.Type;
        mi.MappingSchema   = mappingSchema;
        mi.MemberExtension = _extension[ma.Name];
        mi.Name            = GetFieldName   (ma);
        mi.MemberName      = ma.Name;
        mi.Trimmable       = GetTrimmable   (ma);
        mi.MapValues       = GetMapValues   (ma);
        mi.DefaultValue    = GetDefaultValue(ma);
        mi.Nullable        = GetNullable    (ma);
        mi.NullValue       = GetNullValue   (ma, mi.Nullable);

        Add(CreateMemberMapper(mi));

        if (!TypeHelper.IsScalar(ma.Type))
        {
            ObjectMapper com = Map.GetObjectMapper(ma.Type);
            foreach (MemberMapper cmm in com)
            {
                Add(CreateComplexMapper(cmm, ma));
            }
        }
    }

    /* А это выкинуть за ненадобностью. А можно и оставить, чтобы и First и Name.First работали.
    foreach (AttributeExtension ae in _extension.Attributes["MapField"])
    {
        string mapName  = (string)ae["MapName"];
        string origName = (string)ae["OrigName"];

        if (mapName == null || origName == null)
            throw new MappingException(string.Format(
                "Type '{0}' has invalid  extension. MapField MapName='{1}' OrigName='{2}'.",
                    type.FullName, mapName, origName));

        EnsureMapper(mapName, origName);
    }

    foreach (MapFieldAttribute attr in MapFieldAttributes)
        EnsureMapper(attr.MapName, attr.OrigName);
    */
}


Тормозов это не прибавит, кроме экзотического случая, когда вложенный класс заявлен для мапинга, но на деле не используется.
Если вложенных классов нет — то нет и тормозов.

P.S. Игорь, если ты не против, но сильно занят, то гони ключи от квартиры, буду сам переделывать.
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re: Вопрос по вложенным классам
От: IT Россия linq2db.com
Дата: 29.05.06 16:57
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>Игорь! разъясни, пожалуйста, генеральную политику партии в отношении вложенных классов.


БП>Во всему выходит, что это
Автор(ы): Игорь Ткачёв
Дата: 01.07.2003
В статье подробно рассматривается состав и способы применения пространства имён Rsdn.Framework.Data, представляющего собой высокоуровневую обёртку над ADO.NET.
единственное, что с ними можно делать.


Не единственное. Как такой вариант?

public abstract class RecordHeader : EditableObject
{
    [MaxLength(50), Required]      public abstract string Last   { get; set; }
    [MaxLength(50), Required]      public abstract string First  { get; set; }
    [MaxLength(50), NullValue("")] public abstract string Middle { get; set; }
}

[TableName("Person")]
[MapField("FirstName",  "Name.First")]
[MapField("LastName",   "Name.Last")]
[MapField("MiddleName", "Name.Middle")]
public abstract class Person : EditableObject
{
    [Required]                 public abstract Gender       Gender { get; set; }
                               public abstract RecordHeader Name   { get; set; }
    [PrimaryKey, NonUpdatable]
    [MapField("PersonID")]     public abstract int          ID     { get; set; }
}

БП>Давайте мапить поля вложенных классов без префикса, а кому очень нужно, пусть задают его явно.

Подожди. Давай сначала решим устраивает ли тебя предложенный выше вариант.

БП>P.S. Игорь, если ты не против, но сильно занят, то гони ключи от квартиры, буду сам переделывать.


Да, видимо пора.
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: Вопрос по вложенным классам
От: Блудов Павел Россия  
Дата: 30.05.06 02:30
Оценка:
Здравствуйте, IT, Вы писали:

IT>Не единственное. Как такой вариант?

IT>
IT>[MapField("FirstName",  "Name.First")]
IT>[MapField("LastName",   "Name.Last")]
IT>[MapField("MiddleName", "Name.Middle")]
IT>public abstract class Person : EditableObject
IT>{
IT>


Вариант канает, но требует установки атрибутов у внешнего класса. Красота и простота теряются.
Проблема нынешней реализации вложенных классов в том, что комплексные маперы создаются по требованию. При этом нужно знать имя.
Т.е. можно легко нарваться на ситуацию, когда в момент времени А у объекта N MemberMapper'ов, а в момент B их уже N + M, т.к. поtearoffились несколько комплексных маперов. В тестах всё может и работает как часики, а в реальной жизни у честных людей полезут странные и невоспроизводимые горбухи.
Предложенный тобой способ служит для одной цели: задать комплекстные маперы заранее, чтобы они не создавались на ходу.

Тут требуются переделки в консерватории. У меня usecase'ы для вложенных типы получились такие:
Самое простое решение это тупо создавать комплексные маперы и не добавлять префикс. Первое у меня сомнений не вызывает, а вот со вторым всё не так и не просто.
Как минимум, нужно учитывать совместимость с предыдущими версиями. Кроме того, префикс не даст наступить на грабли с одинаковыми именами. В тоже время точку трудно использовать в имени параметра спрока. Да и в имени поля записи ей тоже не место. Вобщем

Могу предложить компромисный вариант: генерацию префикса контролировать атрибутом, и по умолчанию сделать его вкыл. Вот так
public abstract class RecordHeader
{
    public abstract string Last { get; set; }
    public abstract string First { get; set; }
    public abstract string Middle { get; set; }
}

public abstract class Person
{
    [MapIgnore(false), Prefix(null)]
    public abstract RecordHeader Name { get; set; } // Префикса нету; Last, First & Middle попадают в Person как родные.
    [MapIgnore(false), Prefix("Foo")]
    public abstract RecordHeader AlsoName { get; set; } // Last, First & Middle попадают в Person как Foo.Last, Foo.First & Foo.Middle
    [MapIgnore(false)]
    public abstract RecordHeader NameAgain { get; set; } // Last, First & Middle попадают в Person как NameAgain.Last, NameAgain.First & NameAgain.Middle
}

Тогда копипастить вложенный тип будет проще. Трудно скопировать поле и забыть его атрибуты.
Недостаток этого решения в том, что я не уверен, что смогу его реализовать ничего не поломав. Но буду стараться. Если, конечно, нет способа лучше .

Теперь о багах. Вот такой тест стреляется:
        public abstract class RecordHeader
        {
            public abstract string Last { get; set; }
            public abstract string First { get; set; }
            public abstract string Middle { get; set; }
        }

        public abstract class Person
        {
            [MapIgnore(false)]
            public abstract RecordHeader Name { get; set; }
            public abstract int ID { get; set; }
        }

        [Test]
        public void InnerTest()
        {
            ObjectMapper om = Map.GetObjectMapper(typeof(Person));

            Person o = (Person)om.CreateInstance();

            om.SetValue(o, "Name.First", "Crazy");
            om.SetValue(o, "Name.Last", "Frog");

            Person p2 = Map.ObjectToObject<Person>(o);

            Assert.AreEqual(p2.Name.First, o.Name.First);
            Assert.AreEqual(p2.Name.Last, o.Name.Last);
        }


Выяснилось, что в BLToolkit.Mapping.MemberMapper.MapFrom есть вот такая строчка:
            // ...
            Type valueType  = value.GetType();
            Type memberType = mapInfo.Type;

            if (valueType != memberType)
            {
            // ...


Здесь valueType == "Mapping.MapFieldAttributeTest.BLToolkitExtension.RecordHeader",
а memberType == "Mapping.MapFieldAttributeTest+RecordHeader"
В коране
Автор(ы): Игорь Ткачёв
Дата: 01.07.2003
В статье подробно рассматривается состав и способы применения пространства имён Rsdn.Framework.Data, представляющего собой высокоуровневую обёртку над ADO.NET.
сказано, что так и должно быть, и что лечится это заменой явного сравнения на проверку ДНК. И, действительно, вылечилось заменой на
if (!memberType.IsAssignableFrom(valueType))


Но всё оказалось куда как запущеней... Дело в том, что RecordHeader это класс и при мапиньи p2 получил от o ссылку на Name Теперь если изменить
o.Name.First = "Foo"
то и
p2.Name.First станет "Foo"
Приплыли.
Здесь нужно что-то из разряда System.ComponentModel.ImmutableObject. Т.е. не скалярные типы должны или клонироваться, или для них нужно заводить мапера и мапить по полям. Может я чего-то недопонимаю? Меня смущает тот факт, что RecordHeader вообще попал в членомаперы. Этой проблемы вообще бы не случилось, если вместо членомапера для поля Name оказались три мапера для его полей.
Кстати, в твоём варианте,
IT>
IT>[MapField("FirstName",  "Name.First")]
IT>[MapField("LastName",   "Name.Last")]
IT>[MapField("MiddleName", "Name.Middle")]
IT>public abstract class Person : EditableObject
IT>{
IT>

Происходит вообще что-то подозрительное. Сначала мапится поле Name, т.е. p2.Name и o.Name указывают на один и тот же объект, затем трижды происходит складывание строк туда же, где они и находятся.

БП>>P.S. Игорь, если ты не против, но сильно занят, то гони ключи от квартиры, буду сам переделывать.

IT>Да, видимо пора.
Ты, это, в месенджере объявись. У меня paul(at)ozero.net
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[3]: Вопрос по вложенным классам
От: IT Россия linq2db.com
Дата: 30.05.06 02:34
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>Ты, это, в месенджере объявись. У меня paul(at)ozero.net


Offline
Если нам не помогут, то мы тоже никого не пощадим.
Re[4]: Вопрос по вложенным классам
От: Блудов Павел Россия  
Дата: 30.05.06 02:56
Оценка:
Здравствуйте, IT, Вы писали:

IT>Offline

Странно. Я даже пере signin'улся. Всё вроде работает. Правда месенджер у меня старинный, Windows Messenger 4.7.3000. Может нужно msn messenger поставить?
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[3]: Вопрос по вложенным классам
От: maldi  
Дата: 02.06.06 15:47
Оценка:
Добрый день.

IT>>Не единственное. Как такой вариант?

IT>>
IT>>[MapField("FirstName",  "Name.First")]
IT>>[MapField("LastName",   "Name.Last")]
IT>>[MapField("MiddleName", "Name.Middle")]
IT>>public abstract class Person : EditableObject
IT>>{
IT>>


БП>Вариант канает, но требует установки атрибутов у внешнего класса. Красота и простота теряются.

БП>Проблема нынешней реализации вложенных классов в том, что комплексные маперы создаются по требованию. При этом нужно знать имя.
БП>Т.е. можно легко нарваться на ситуацию, когда в момент времени А у объекта N MemberMapper'ов, а в момент B их уже N + M, т.к. поtearoffились несколько комплексных маперов. В тестах всё может и работает как часики, а в реальной жизни у честных людей полезут странные и невоспроизводимые горбухи.
БП>Предложенный тобой способ служит для одной цели: задать комплекстные маперы заранее, чтобы они не создавались на ходу.

Чем всё закончилось? Есть какие-то альтернативные способы привязки вложенных классов без явного перечисления всех их полей?
Re[4]: Вопрос по вложенным классам
От: IT Россия linq2db.com
Дата: 02.06.06 23:06
Оценка:
Здравствуйте, maldi, Вы писали:

M>Чем всё закончилось? Есть какие-то альтернативные способы привязки вложенных классов без явного перечисления всех их полей?


Сошлись на явном перечислении. Тебя этот вариант не устраивает?
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Re[5]: Вопрос по вложенным классам
От: Блудов Павел Россия  
Дата: 06.06.06 02:12
Оценка: +1
Здравствуйте, IT!

Вылезл ещё один тонкий момент. В DataAccessBuilder'е имеем:
        public FieldBuilder GetIndexField(string[] index)
        {
            string id = "index$" + string.Join(".", index);

Так вот, в случае с вложенными классами можно получить чужой индекс. Как-то так:
public abstract class RecordHeader : EditableObject
{
    [MaxLength(50), Required]
    public abstract string Last { get; set; }
    [MaxLength(50), Required]
    public abstract string First { get; set; }
    [MaxLength(50), NullValue("")]
    public abstract string Middle { get; set; }
}

public abstract Person
{
    [MapIgnore(false)]
    public abstract RecordHeader Name { get; set; }
    [PrimaryKey, NonUpdatable]
    public abstract string First { get; set; }
}


Тут GetIndexField сгородит одинаковый идентификатор для индекса MapIndex(Name.First) и для MapIndex(Name, First).
Я точку конечно заменю на что-то более экзотическое (две, нет, три! точки ) но проблему в корне это не решит, т.к. всегда можно посмотреть рефлектором имя переменной для индекса и задать его через MapFieldAttribute. На это закладываться смысла нет, чтобы не скатиться до гиудов, но точку точно нужно заменить.
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[5]: Вопрос по вложенным классам
От: Аноним  
Дата: 06.06.06 14:02
Оценка:
Здравствуйте, IT, Вы писали:

IT>Здравствуйте, maldi, Вы писали:


M>>Чем всё закончилось? Есть какие-то альтернативные способы привязки вложенных классов без явного перечисления всех их полей?


IT>Сошлись на явном перечислении. Тебя этот вариант не устраивает?


Да в принципе сойдёт и такой. Просто комплексных полей может быть много, не хотелось перечислять все их члены в одном месте. Да и меняться они могут иногда, а это приведёт к двойному исправлению.
Re[6]: Вопрос по вложенным классам
От: IT Россия linq2db.com
Дата: 07.06.06 01:43
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Да в принципе сойдёт и такой. Просто комплексных полей может быть много, не хотелось перечислять все их члены в одном месте. Да и меняться они могут иногда, а это приведёт к двойному исправлению.


Попробуй повесить эти атрибуты на интерфейс и наследоваться от этого интерфейса. Можно в базовый класс засунуть, но можно словить глюков.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Re: Вопрос по вложенным классам
От: vozamlA  
Дата: 12.07.06 20:13
Оценка:
Добрый вечер.

Имея такой код

    namespace BLTTest
{
    [TableName("Rules")]
    [MapField("RuleSetID", "RuleSetInfo.ID")]
    [MapField("RuleSetName", "RuleSetInfo.Name")]
        //Правило
    public abstract class Rule
    {
        [MapField("RuleID")]
        public abstract int ID { get; set; }

        [MapField("RuleName")]
        public abstract String Name { get; set; }

        public abstract RuleSet RuleSetInfo { get; set; }
    }

    [TableName("RuleSets")]
        ////Набор правил
    public abstract class RuleSet
    {
        [MapField("RuleSetID")]
        public abstract int ID { get; set; }

        [MapField("RuleSetName")]
        public abstract String Name { get; set; }
    }
}


я получаю коллекцию правил(Rule) посредством хранимой процедуры.Однако же, когда я пытаюсь после некоторой правки сделать UpdateSql, то получаю ошибку
в связи с отсутствием в таблице Rules поля RuleName(RuleSetID в ней присутствует). Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.

Заранее благодарен.
Re[2]: Вопрос по вложенным классам
От: IT Россия linq2db.com
Дата: 12.07.06 21:24
Оценка: 3 (1)
Здравствуйте, vozamlA, Вы писали:

A>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.


Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.

По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.
Если нам не помогут, то мы тоже никого не пощадим.
Re[3]: Вопрос по вложенным классам
От: vozamlA  
Дата: 13.07.06 08:15
Оценка:
Здравствуйте, IT, Вы писали:

IT>Здравствуйте, vozamlA, Вы писали:


A>>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.


IT>Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.


IT>По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.


Спасибо за ответ.
Не могли бы Вы продемострировать в двух строчках, как это будет выглядеть в коде и в xml'е. Я прошу это сделать, по скольку мне тоже приходила в голову эта мысль, но реализовать(с использованием xml расширения) не получилось.
Re[4]: Вопрос по вложенным классам
От: vozamlA  
Дата: 03.08.06 20:00
Оценка:
Здравствуйте, vozamlA, Вы писали:

A>Здравствуйте, IT, Вы писали:


IT>>Здравствуйте, vozamlA, Вы писали:


A>>>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.


IT>>Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.


IT>>По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.


A>Спасибо за ответ.

A>Не могли бы Вы продемострировать в двух строчках, как это будет выглядеть в коде и в xml'е. Я прошу это сделать, по скольку мне тоже приходила в голову эта мысль, но реализовать(с использованием xml расширения) не получилось.

Так и не получилось реализовать такую фичу . Может кто — нибудь решал эту проблему,поделитесь кодом пожалуйста.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.