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. Игорь, если ты не против, но сильно занят, то гони ключи от квартиры, буду сам переделывать.
Здравствуйте, Блудов Павел, Вы писали:
БП>Игорь! разъясни, пожалуйста, генеральную политику партии в отношении вложенных классов.
БП>Во всему выходит, что это
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. Игорь, если ты не против, но сильно занят, то гони ключи от квартиры, буду сам переделывать.
Да, видимо пора.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, 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'ы для вложенных типы получились такие:
Всё должно быть максимально очевидно и интуитивно понятно.
Все членомаперы должны быть доступны для IEnumerate'ния сразу по созданию мапера.
Вложенные типы должны учитываться в CRUDL операциях и в вызове хранимок из датаакцесоров.
Вложенные типы могут быть структурами, классами и абстрактными классами. (Тут всё почти ок, об этом дальше.)
Самое простое решение это тупо создавать комплексные маперы и не добавлять префикс. Первое у меня сомнений не вызывает, а вот со вторым всё не так и не просто.
Как минимум, нужно учитывать совместимость с предыдущими версиями. Кроме того, префикс не даст наступить на грабли с одинаковыми именами. В тоже время точку трудно использовать в имени параметра спрока. Да и в имени поля записи ей тоже не место. Вобщем
Могу предложить компромисный вариант: генерацию префикса контролировать атрибутом, и по умолчанию сделать его вкыл. Вот так
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"
В коране
сказано, что так и должно быть, и что лечится это заменой явного сравнения на проверку ДНК. И, действительно, вылечилось заменой на
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
Здравствуйте, IT, Вы писали:
IT>Offline
Странно. Я даже пере signin'улся. Всё вроде работает. Правда месенджер у меня старинный, Windows Messenger 4.7.3000. Может нужно msn messenger поставить?
Добрый день.
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ились несколько комплексных маперов. В тестах всё может и работает как часики, а в реальной жизни у честных людей полезут странные и невоспроизводимые горбухи. БП>Предложенный тобой способ служит для одной цели: задать комплекстные маперы заранее, чтобы они не создавались на ходу.
Чем всё закончилось? Есть какие-то альтернативные способы привязки вложенных классов без явного перечисления всех их полей?
Здравствуйте, maldi, Вы писали:
M>Чем всё закончилось? Есть какие-то альтернативные способы привязки вложенных классов без явного перечисления всех их полей?
Сошлись на явном перечислении. Тебя этот вариант не устраивает?
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Вылезл ещё один тонкий момент. В 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>Сошлись на явном перечислении. Тебя этот вариант не устраивает?
Да в принципе сойдёт и такой. Просто комплексных полей может быть много, не хотелось перечислять все их члены в одном месте. Да и меняться они могут иногда, а это приведёт к двойному исправлению.
Здравствуйте, <Аноним>, Вы писали:
А>Да в принципе сойдёт и такой. Просто комплексных полей может быть много, не хотелось перечислять все их члены в одном месте. Да и меняться они могут иногда, а это приведёт к двойному исправлению.
Попробуй повесить эти атрибуты на интерфейс и наследоваться от этого интерфейса. Можно в базовый класс засунуть, но можно словить глюков.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
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 методы.
Здравствуйте, vozamlA, Вы писали:
A>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.
Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.
По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, vozamlA, Вы писали:
A>>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.
IT>Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.
IT>По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.
Спасибо за ответ.
Не могли бы Вы продемострировать в двух строчках, как это будет выглядеть в коде и в xml'е. Я прошу это сделать, по скольку мне тоже приходила в голову эта мысль, но реализовать(с использованием xml расширения) не получилось.
Здравствуйте, vozamlA, Вы писали:
A>Здравствуйте, IT, Вы писали:
IT>>Здравствуйте, vozamlA, Вы писали:
A>>>Расскажите, пожалуйста, как можно решить эту архитектурную проблему. Сразу скажу, что не хотелось бы для CRUDL операций аксессора таблицы правил(Rules) реализовывать хранимые процедуры и переопределять его CRUDL методы.
IT>>Можно использовать NonUpdatableAttribute. К сожалению его нельзя применить к свойству RuleSetInfo непосредтсвенно. Но пожно применить к полям класса RuleSet. Это в свою очередь породит другую проблему — не будут работать CRUDL операции для RuleSet.
IT>>По идее NonUpdatableAttribute хорошо бы расширить и использовать его на уровне класса с явным указанием имени поля. Пока, без переделки тулкита, можно пропробовать использовать xml расширение. В нём можно задать NonUpdatableAttribute атрибут для таких полей.
A>Спасибо за ответ. A>Не могли бы Вы продемострировать в двух строчках, как это будет выглядеть в коде и в xml'е. Я прошу это сделать, по скольку мне тоже приходила в голову эта мысль, но реализовать(с использованием xml расширения) не получилось.
Так и не получилось реализовать такую фичу . Может кто — нибудь решал эту проблему,поделитесь кодом пожалуйста.