Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, stump, Вы писали:
S>>Если вернутся к многострадальному методу AddOrderLine, то он, несомненно, должен оставить ордер в "сформированном" состоянии, т.е. расчитать НДС пересчитать скидки, тоталы, сабтоталы и т.д. и т.п. C>Ок. У нас сделана система, которая раз в 30 минут печатает на бумаге все outstanding orders и посылает их на почту. Пользователь добавляет первую строку, вторую, третюю. Тут врубается демон и отсылает полусформированый заказ. Упс.
Данная проблема решается с помощью выбора грануляции бизнес методов удаленного интерфейса и не является препятствием к использованию подхода, предлагаемого stump-ом. Достаточно изменить сигнатуру так, чтобы все OrderLine-ы ушли одним вызовом.
C>Ещё другая деталь. Ладно, мы отослали AddOrderLine, и сервер пересчитал суммы, налоги и скидки. Но как эти изменения отобразить клиенту? Что он должен получить из метода AddOrderLine? Весь изменённый граф объектов или просто void?
Тут рулят требования к системе. Если проведение бизнес операции на клиенте не допустимо в принципе, то тогда клиенту в любом случае придется запрашивать обновленные данные. Результатом операции так же может быть диагностическая информация (аки результат валидации на сервере). И только в том случае, когда проведение бизнес операции проводится на клиенте а сервер только лишь фиксирует ее, можно не посылать изменения с сервера на клиент. В общем, тут на принятие решения влияют более высокие материи, нежели эффективность передачи графа объектов.
Re[9]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, stump, Вы писали:
S>>Если вернутся к многострадальному методу AddOrderLine, то он, несомненно, должен оставить ордер в "сформированном" состоянии, т.е. расчитать НДС пересчитать скидки, тоталы, сабтоталы и т.д. и т.п. C>Ок. У нас сделана система, которая раз в 30 минут печатает на бумаге все outstanding orders и посылает их на почту. Пользователь добавляет первую строку, вторую, третюю. Тут врубается демон и отсылает полусформированый заказ. Упс.
Пользователь может редактировать заказ час и два. Сохраниять его и опять редакторовать, и так может продолжаться днями и неделями. Я в курсе...
Давайте больше не будем играть в эту игру: вы придумываете новые требования, а я придумываю как их реализовать.
Совершенно очевидно, что в нормальной системе пользователю не дадут вводить позиции в заказ ушедший в обработку, а система не станет обрабатывать заказы которые редактируются пользователями.
Если вы не в курсе, как решаются подобные проблемы, изучайте профессиональную литературу и открытые источники в интернете.
К тому же подобная проблема стоит перед разработчиком независимо от того, какой тип фасада он использует.
C>Опять же, каждое добавление строки или другое изменение — это сетевой вызов. А это тормоза интерфейса, особенно если вызов идёт через Инет по медленным каналам. Ну и нагрузка на сервер, конечно.
Да, этот аспект оказывает влияние на проектирование интерфейса, его уже обсуждали здесь.
C>Ещё другая деталь. Ладно, мы отослали AddOrderLine, и сервер пересчитал суммы, налоги и скидки. Но как эти изменения отобразить клиенту? Что он должен получить из метода AddOrderLine? Весь изменённый граф объектов или просто void?
Возможен любой вариант, все зависит от совокупности требований.
Здравствуйте, samius, Вы писали:
>Здесь меня немного пугает обилие кода, связанным с поддержкой сообщений об изменении DTO объектов и довольно плотной подпиской на все сообщения ClientContext-ом.
Есть предположение, что можно написать вот такой базовый класс для DTO
public class DataTransferObjectBase<TLinqToSqlObject>
{
protected T GetValue<T>(Expression<Func<TLinqToSqlObject, T>> expression)
{
//TODO: implement thisthrow new NotImplementedException();
}
protected void SetValue<T>(Expression<Func<TLinqToSqlObject, T>> expression, T value)
{
//TODO: implement thisthrow new NotImplementedException();
}
protected IEnumerable<T> GetValue<T>(
Expression<Func<TLinqToSqlObject, EntitySet<T>>> expression) where T : class
{
//TODO: implement thisthrow new NotImplementedException();
}
}
пример конкретного DTO
public class OrderDTO : DataTransferObjectBase<Order>
{
public DateTime? OrderDate
{
get
{
return GetValue(x => x.OrderDate);
}
set
{
SetValue(x => x.OrderDate, value);
}
}
public Customer Customer
{
get
{
return GetValue(x => x.Customer);
}
set
{
SetValue(x => x.Customer, value);
}
}
public IEnumerable<OrderDetail> OrderDetails
{
get
{
return GetValue(x => x.OrderDetails);
}
}
}
Сообщения об изменениях, на которые подписывается ClientContext, будут реализованы в базовом классе DataTransferObjectBase. Помимо этого в базовом классе реализуется логика управления связями между DTO объектами, например, OrderDTO.Customer, OrderDTO.OrderDetails.
Второй вариант -- сделать DTO автогенеренными.
>Накопление истории изменений и применение ее на сервере тоже может оказаться громоздким.
Если говорить схематично, история изменений будет накапливаться в виде простой структуры ("Тип LINQtoSQL объекта", "Значение Primary Key-я", "Имя свойства", "Значение свойства"). Возможно, потребуется передача значений всех свойств. Кроме того, будут аналогичные схемы для insert и delete. Еще timpstamp-ы. Но все это будет выполнятся в инфраструктурных классах, и вроде не так уж это сложно получается.
>Если нет — могу предложить вариант, где ClaimDTO и граф объектов являются надстройкой над типизированным DataSet-ом.
Мы рассматривали возможность использования DataSet-ов в качестве DTO. DataSet все-таки отличается от графа связанных объектов. В случае графа -- если объекты графа теряют ссылку на какой-либо объект, то он уже не пренадлежит этому графу, и его уберет сборщик мусора. В случае DataSet-а такого нет -- DataSet жестко хранит все DataRow в себе. Пример, в котором это проявляется, это сценарий с Lookup-ом. Lookup -- это поле на форме, значение поля можно выбирать из списка, список может быть большим, возможно, с пейджингом (например, список продуктов). Если пользователь меняет значение Lookup-а, то в случае графа объекта мы просто выставляем новое значение
orderItem.Product = newProduct
Новый продукт присоединился к графу, старый ушел к сборщику мусора. newProduct берется из списка, который показывается пользователю, либо по Id подгружается с сервера.
В случае с DataSet-ом, у нас проблемы. Мерджить DataSet-ы? или не иметь свойство Product у orderItem, и работать с двумя датасетами и выдергивать Product из второго DataSet-а по Id?
ClientContext (лучше его назвать просто ChangesTracker) не накладывает такой жесткой связи на множество объектов как DataSet. Между DataSet-ом и DataRow двухстороння связь -- DataSet знает о DataRow, и DataRow знает о DataSet. В случает ClientContext-а -- DtoObject хранит ссылку на ClientContext (точнее на делегат из ClientContext), а ClientContext не хранит ссылку на DtoObject.
>При использовании LINQ to SQL, такой DataSet вообще не обязан повторять схему БД, может содержать любую избыточную информацию и служить буфером для изменений в ClaimDTO или в схеме БД.
Схему типизированного DataSet-а каждый раз рисовать вручную?
Re[3]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, Alexander Polyakov, Вы писали:
>>Накопление истории изменений и применение ее на сервере тоже может оказаться громоздким. AP>Если говорить схематично, история изменений будет накапливаться в виде простой структуры ("Тип LINQtoSQL объекта", "Значение Primary Key-я", "Имя свойства", "Значение свойства"). Возможно, потребуется передача значений всех свойств. Кроме того, будут аналогичные схемы для insert и delete. Еще timpstamp-ы. Но все это будет выполнятся в инфраструктурных классах, и вроде не так уж это сложно получается.
Я вот не пойму никак, а зачем вам все это? Зачем накапливать историю изменений в клиентском контексте в таком странном виде? Когда ее передавать на сервер? Что делать если свойство объекта изменили пять раз подряд и в конце оно приобрело то-же значение, что и до изменений? Что делать если объект добавили в клиентском контексте, а потом сохранили на него ссылки в других объектах, а потом удалилив клиентском контексте, как вы будете разруливать ссылочную целостность при сохранении на сервере? Почему не прердавать сами измененные объекты? У вас в системе кроме данных никакой логики нет? Что если при изменении свойства OrderDTO.Customer система должна отправить email определенного содержания?
По моему вы сейчас на ровном месте создаете себе кучу проблем, которые потом будете героически преодолевать.
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, samius, Вы писали:
>>Здесь меня немного пугает обилие кода, связанным с поддержкой сообщений об изменении DTO объектов и довольно плотной подпиской на все сообщения ClientContext-ом. AP>Есть предположение, что можно написать вот такой базовый класс для DTO
AP> public class DataTransferObjectBase<TLinqToSqlObject>
AP> {
AP> }
AP>
Написать то можно, будет ли он работать с LINQ2SQL — вот в чем вопрос! Точнее работать то будет, но будут определенного характера проблемы при попытке сериализации LINQ объектов. Даже если привязать к ним атрибут [Serializable], воспользовавшись partial объявлениями, все равно эти объекты содержат несериализуемые поля типа EntitySet<T>. Еще раз повторюсь, обратите внимание на EntityFramework, его объекты сериализуемы по определению! Кроме того, их можно передавать обратно на сервер и там аттачить к контексту с применением всех изменений. Очень может быть, что их и оборачивать не придется.
AP> public class OrderDTO : DataTransferObjectBase<Order>
AP> {
AP> public DateTime? OrderDate
AP> {
AP> get
AP> {
AP> return GetValue(x => x.OrderDate);
return this.LinqObject.OrderDate; // так не проще?
AP> }
AP> }
AP> public Customer Customer
public CustomerDTO Customer
Вообще, все объекты, которые можно получить из методов DTO объекта, должны быть DTO объектами. Иначе при обращении к свойствам orderDto.Customer.*** не будет происходить фиксации изменений в истории.
AP> {
А здесь придется иметь дело с IdentityMap-ом
AP> get
AP> {
AP> return GetValue(x => x.Customer);
AP> }
AP> set
AP> {
AP> SetValue(x => x.Customer, value);
AP> }
AP> }
AP> public IEnumerable<OrderDetail> OrderDetails
public IEnumerable<OrderDetailDTO> OrderDetails
AP> {
AP> }
AP> }
AP>
AP>Сообщения об изменениях, на которые подписывается ClientContext, будут реализованы в базовом классе DataTransferObjectBase. Помимо этого в базовом классе реализуется логика управления связями между DTO объектами, например, OrderDTO.Customer, OrderDTO.OrderDetails.
Не связывался бы я на вашем месте с сообщениями у каждого экземпляра DTO. Раз уж на то пошло, то проще привязать DTO к контексту, чтобы он сам записывал в контекст свои изменения. А сообщения сделать у контекста.
AP>Второй вариант -- сделать DTO автогенеренными.
Сначала нужно решить все принципиальные проблемы. Одна небольшая деталь (типа несериализуемости LINQ объекта) и вся концепция в корзине!
>>Накопление истории изменений и применение ее на сервере тоже может оказаться громоздким. AP>Если говорить схематично, история изменений будет накапливаться в виде простой структуры ("Тип LINQtoSQL объекта", "Значение Primary Key-я", "Имя свойства", "Значение свойства"). Возможно, потребуется передача значений всех свойств. Кроме того, будут аналогичные схемы для insert и delete. Еще timpstamp-ы. Но все это будет выполнятся в инфраструктурных классах, и вроде не так уж это сложно получается.
не советую связываться с timestamp-ами, особенно в распределенной системе. Даже если все машины в одном домене с общим time сервером, будут проблемы. Лучше счетчики изменений. А еще лучше отказаться от истории.
>>Если нет — могу предложить вариант, где ClaimDTO и граф объектов являются надстройкой над типизированным DataSet-ом. AP>Мы рассматривали возможность использования DataSet-ов в качестве DTO. DataSet все-таки отличается от графа связанных объектов. В случае графа -- если объекты графа теряют ссылку на какой-либо объект, то он уже не пренадлежит этому графу, и его уберет сборщик мусора. В случае DataSet-а такого нет -- DataSet жестко хранит все DataRow в себе. Пример, в котором это проявляется, это сценарий с Lookup-ом. Lookup -- это поле на форме, значение поля можно выбирать из списка, список может быть большим, возможно, с пейджингом (например, список продуктов). Если пользователь меняет значение Lookup-а, то в случае графа объекта мы просто выставляем новое значение AP>Новый продукт присоединился к графу, старый ушел к сборщику мусора. newProduct берется из списка, который показывается пользователю, либо по Id подгружается с сервера. AP>В случае с DataSet-ом, у нас проблемы. Мерджить DataSet-ы? или не иметь свойство Product у orderItem, и работать с двумя датасетами и выдергивать Product из второго DataSet-а по Id?
Вы хотите мержить графы объектов и обновлять тем самым граф DTO по частям? В общем случае это возможно только без физического удаления записей. Да и вообще проблем это создаст больше, чем вы решите.
Вечных DTO лучше не делать. Они должны регулярно обновляться полностью, старый DTO соберет GC, на чем бы он ни был написан. Одним DTO на все задачи вы не обойдетесь. И мержить их не надо. В одном DTO накачены данные очередной страницы списка, при переходе к бизнес-задачам заказываете другой DTO с данными для этой задачи. Иначе для педжинга придется пол базы данных джойнить, а это не есть гуд. AP>ClientContext (лучше его назвать просто ChangesTracker) не накладывает такой жесткой связи на множество объектов как DataSet. Между DataSet-ом и DataRow двухстороння связь -- DataSet знает о DataRow, и DataRow знает о DataSet. В случает ClientContext-а -- DtoObject хранит ссылку на ClientContext (точнее на делегат из ClientContext), а ClientContext не хранит ссылку на DtoObject.
Это ничего не решает, т.к. без IdentityMap-а вам похоже не обойтись. Либо выкидывать отработавшие DTO полностью, либо мудрить со слабыми ссылками, но дело это не благодарное.
>>При использовании LINQ to SQL, такой DataSet вообще не обязан повторять схему БД, может содержать любую избыточную информацию и служить буфером для изменений в ClaimDTO или в схеме БД. AP>Схему типизированного DataSet-а каждый раз рисовать вручную?
Да, это нормально. Или уже сейчас ничего вручную не пишется кроме генераторов? Тесты за Вас тоже генерируются?
stump>По моему вы сейчас на ровном месте создаете себе кучу проблем, которые потом будете героически преодолевать.
Согласен. Тоже от этого страдаю, правда со возрастом все меньше
Re[4]: Покритикуйте архитектуру обмена данными для smart кли
Да, извиняюсь, я немного поторопился. Естественно, DTO объекты ссылаются только на DTO объекты. Схема становиться менее красивой, появляются касты
public class DataTransferObjectBase<TLinqToSqlObject>
{
protected T GetValue<T>(Expression<Func<TLinqToSqlObject, T>> expression)
{
//TODO: implement thisthrow new NotImplementedException();
}
protected void SetValue<T>(Expression<Func<TLinqToSqlObject, T>> expression, T value)
{
//TODO: implement thisthrow new NotImplementedException();
}
protected IEnumerable<DataTransferObjectBase<T>> GetValue<T>(
Expression<Func<TLinqToSqlObject, EntitySet<T>>> expression) where T : class
{
//TODO: implement thisthrow new NotImplementedException();
}
protected DataTransferObjectBase<T> GetRef<T>(Expression<Func<TLinqToSqlObject, T>> expression)
{
//TODO: implement thisthrow new NotImplementedException();
}
protected void SetRef<T>(
Expression<Func<TLinqToSqlObject, T>> expression, DataTransferObjectBase<T> value)
{
//TODO: implement thisthrow new NotImplementedException();
}
}
public class OrderDTO : DataTransferObjectBase<Order>
{
public DateTime? OrderDate
{
get
{
return GetValue(x => x.OrderDate);
}
set
{
SetValue(x => x.OrderDate, value);
}
}
public CustomerDTO Customer
{
get
{
return (CustomerDTO)GetRef(x => x.Customer);
}
set
{
SetRef(x => x.Customer, value);
}
}
public IEnumerable<OrderDetailsDTO> OrderDetails
{
get
{
return GetValue(x => x.OrderDetails).Cast<OrderDetailsDTO>();
}
}
}
public class CustomerDTO : DataTransferObjectBase<Customer>
{
}
public class OrderDetailsDTO : DataTransferObjectBase<OrderDetailsDTO>
{
}
Замечу, что методы принимают Expression, а не делегаты.
Объекты LINQtoSQL не обязаны быть сериализуемыми, классы LINQtoSQL выступают только как типы, инстансов LINQtoSQL объектов в DTO нет.
Re[5]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Замечу, что методы принимают Expression, а не делегаты. AP>Объекты LINQtoSQL не обязаны быть сериализуемыми, классы LINQtoSQL выступают только как типы, инстансов LINQtoSQL объектов в DTO нет.
Тогда, если честно, я не догоняю, откуда будет браться на клиенте значение OrderDTO.OrderDate и куда складываться? И все остальое...
Re[6]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, samius, Вы писали:
S>Тогда, если честно, я не догоняю, откуда будет браться на клиенте значение OrderDTO.OrderDate и куда складываться? И все остальое...
Все складывается во внутренний дикшенери класса DataTransferObjectBase. Референсы и соответствующие Id-шники ведут себя так:
1. Ни какой лайзи подгрузки нет.
2. Если выставляется Id-шник, то соответствующая референс выдает либо эксепшен, либо null (еще не решил что). Старое значение референса сбрасывается.
3. Если выставляется референс, соответствующий Id-шник тоже меняется.
Re[7]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Все складывается во внутренний дикшенери класса DataTransferObjectBase. Референсы и соответствующие Id-шники ведут себя так: AP>1. Ни какой лайзи подгрузки нет. AP>2. Если выставляется Id-шник, то соответствующая референс выдает либо эксепшен, либо null (еще не решил что). Старое значение референса сбрасывается. AP>3. Если выставляется референс, соответствующий Id-шник тоже меняется.
Оу, внутренний словарь? Чего ради тогда эквилибристика с Expression-ами и типами LINQtoSQL объектов?
Re[8]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, samius, Вы писали:
S>Оу, внутренний словарь? Чего ради тогда эквилибристика с Expression-ами и типами LINQtoSQL объектов?
Ради проверок на этапе компиляции. Например,
public DateTime? OrderDate
{
get
{
return GetValue(x => x.OrderDate);
}
set
{
SetValue(x => x.OrderDate, value);
}
}
x => x.OrderDate указывает на свойство LINQ объекта, контролируется имя свойства и тип свойства, например, x => x.OrderDate и value должны быть одного типа.
Ну и интелесенс тоже работает.
Re[9]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, samius, Вы писали:
S>>Оу, внутренний словарь? Чего ради тогда эквилибристика с Expression-ами и типами LINQtoSQL объектов? AP>Ради проверок на этапе компиляции. AP>x => x.OrderDate указывает на свойство LINQ объекта, контролируется имя свойства и тип свойства, например, x => x.OrderDate и value должны быть одного типа. AP>Ну и интелесенс тоже работает.
А как из словаря будет получено значение? И как оно в него попадет? (прошу прощения, не силен пока в expression-ах, не перешли еще на vs2008).
Re[10]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, samius, Вы писали:
S>А как из словаря будет получено значение?
В пробной реализации ключами в словаре будут имена свойств, из expression имя свойства можно получать вот так
((MemberExpression)expression.Body).Member.Name
Да, здесь заточка на экспрешенны только такого вида obj => obj.Property1, иначе исключение в рантайме.
Далее возможно оптимизация, использовать не имя свойства, а порядковый номер, кешировать откомпилированные лямбда выражения и т.д.
S>И как оно в него попадет? (прошу прощения, не силен пока в expression-ах, не перешли еще на vs2008).
Грубо говоря лямбда выражения могут выступать в двух эпостасях:
1. Как замена анонимным делегатам
2. Как System.Linq.Expressions. Тогда компилятор делает AST, по которому можно ходить.
Я использую второй вариант.
Re[11]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, samius, Вы писали: S>>И как оно в него попадет? (прошу прощения, не силен пока в expression-ах, не перешли еще на vs2008). AP>Грубо говоря лямбда выражения могут выступать в двух эпостасях: AP>1. Как замена анонимным делегатам AP>2. Как System.Linq.Expressions. Тогда компилятор делает AST, по которому можно ходить. AP>Я использую второй вариант.
Понятно. Reflection... Хотя, можно использовать его только для заполнения карты делегатов для доставания и складывания значений из свойств LINQ2SQL объектов в словари и обратно (т.е. для работы на сервере)... Но там все может упереться в какую-нибудь проблему типа получения в runtime делегата на Generic метод. В .net 2.0 beta2 это еще работало, в релизе уже не знаю, кажется были изменения на эту тему.
Я все-таки еще раз предложу в качестве основы DTO использовать DataSet (нетипизированный), обращаться к его строкам можно так же как и к словарю, с той разницей, что датасет будет один на все DTO, а словарей будет по одному на каждого... Здорово сэкономите на сериализации (DTO объекты можно будет вообще не сериализовывать, а выращивать на переданном датасете прямо на клиенте), заодно получите всю историю изменений, причем токлько изменения и ничего больше (DataSet.GetChanges()).
Или EntityFramework. Его объекты сериализуемы и не требуют никаких словарей, датасетов и прочего. Кажется, повторяюсь.
Или в конце концов нагенерировать нужный код (Emit или CodeDOM). Писать большие вещи на них муторно, но использовать в сочетании с базовыми классами, написанными руками, довольно просто. Совсем не обязательно emit-ить в продакшн рантайме. Можно, например, создать утилиту, которая загрузит сборку с типами Linq2Sql, после чего анализируя их Reflection-ом наэмитит сериализуемые DTO классы с полями (а не словарем), наэмитит код копирования свойств из DTO в Linq2Sql и обратно (для работы сервера), а потом все это сохранит в dll, которую можно будет использовать при разработке. То же самое касается CodeDOM. Здесь главное — собрать хороший прототип для генерации кода.
Впрочем, если подвиги все же важнее, то не буду мешать
Re[12]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, samius, Вы писали:
S>Понятно. Reflection... Хотя, можно использовать его только для заполнения карты делегатов для доставания и складывания значений из свойств LINQ2SQL объектов в словари и обратно (т.е. для работы на сервере)...
А что плохого в "закешированном" Reflection-e? Проблема пефоманса решается кешированием. Расходы памяти на кеш это мелочи при сегодняшних объемах памяти. Под "закешированном" Reflection-ом я понимаю глобальный на все приложение словарь, из которого по паре ("тип объекта", "имя свойства") можно достать делегат, который дергает геттор/сеттор свойства. Словарь формируется с использованием Dynamic.cs отсюда http://code.msdn.microsoft.com/csharpsamples/Release/ProjectReleases.aspx?ReleaseId=8
Пример использования можно посмотреть здесь http://www.fikrimvar.net/lestirelim/?p=15
S>Я все-таки еще раз предложу в качестве основы DTO использовать DataSet (нетипизированный), обращаться к его строкам можно так же как и к словарю, с той разницей, что датасет будет один на все DTO, а словарей будет по одному на каждого... Здорово сэкономите на сериализации (DTO объекты можно будет вообще не сериализовывать, а выращивать на переданном датасете прямо на клиенте), заодно получите всю историю изменений, причем токлько изменения и ничего больше (DataSet.GetChanges()).
Да, это неплохой подход, и я очень хорошо его знаю, выполнял проект с использованием этого подхода. Давайте поговрим конкретнее. В предыдущем посте я описал конкретный сценарий с лукапом. Как этот сценарий реализовывается на DataSet-ах?
>что датасет будет один на все DTO, а словарей будет по одному на каждого...
в коде словарь тоже только один в базовом классе DataTransferObjectBase. Инстонсов словаря конечно много, в DataRow данные храняться тоже в виде словаря.
S>Или EntityFramework. Его объекты сериализуемы и не требуют никаких словарей, датасетов и прочего. Кажется, повторяюсь.
Да, надо посмотреть.
S>Впрочем, если подвиги все же важнее, то не буду мешать
Нет, нет, никаких подвигов, чистый прагматизм. Мне не известна нормальная платформа для написания Smart client-а, это меня несколько удивляет... Поэтому приходится самим ваять. Вы не мешаете
Re[13]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, Alexander Polyakov, Вы писали:
AP>А что плохого в "закешированном" Reflection-e? Проблема пефоманса решается кешированием. Расходы памяти на кеш это мелочи при сегодняшних объемах памяти. Под "закешированном" Reflection-ом я понимаю глобальный на все приложение словарь, из которого по паре ("тип объекта", "имя свойства") можно достать делегат, который дергает геттор/сеттор свойства.
Примеры посмотрю на днях, щас не до них. S>>Я все-таки еще раз предложу в качестве основы DTO использовать DataSet (нетипизированный), AP>Да, это неплохой подход, и я очень хорошо его знаю, выполнял проект с использованием этого подхода. Давайте поговрим конкретнее. В предыдущем посте я описал конкретный сценарий с лукапом. Как этот сценарий реализовывается на DataSet-ах?
Вас интересует подгрузка нового продукта и внедрение его в старый граф? Вообще, на сколько мне известно, DTO не предполагает сценариев докачки данных. Обычно, с сервера заказываются новые DTO графы, которые полностью подменяют старые. На мой взгляд смешивание графов объектов, полученных с разных сессий, может привести к непредсказуемым последствиям и трудноотлаживаемым багам. Хотя, Merge датасетов не исключен.
>>что датасет будет один на все DTO, а словарей будет по одному на каждого... AP>в коде словарь тоже только один в базовом классе DataTransferObjectBase. Инстонсов словаря конечно много, в DataRow данные храняться тоже в виде словаря.
В виде словаря, но не в словаре. Во всяком случае, сериализация DataSet-ов одна из самых шустрых и компактных в Framework-е. Со словарем специально не сравнивал. В случае 1000 словарей против датасета с 1000 строк делаю ставку на датасет. Но дело не в этом, датасет — это сама история в чистом виде, которую не надо писать, а надо только разгребать на сервере! И то, если пользоваться дедовскими адаптерами ADO.NET, то и разгребать не надо, а прямо сливать в BD. Но похоже это уже не популярно
Re[2]: Покритикуйте архитектуру обмена данными для smart кли
Здравствуйте, stump, Вы писали:
>Я бы не советовал вам связываться с Unit of work и отсылкой изменений в DTO на сервер. >Вместо этого предлагаю вам подумать о развитии сервиса RemoteFacade.
Как Unit of work на клиенте противоречит развитию фасада? У вас методы GetOrder, GetOrderList что-то возвращаю? граф объектов (будем их называть DTO), верно? например, GetOrder покрасней мере возвращает сам объект Order и коллекцию OrderItems (а еще неплохо бы для каждого элемента возвращать Product, и все это за один вызов).
Введение терминов “сервисы данных” и “сервисы бизнес-операций” это конечно хорошо. Но какие бы термины вы не вводили RemoteFacade это всего лишь набор методов со своими аргументами. И весь вопрос в том, аргументы каких типов имеют методы RemoteFacade-а и значения каких типов возвращают (кроме примитивных типов)?
Я утверждаю, что методы возвращают DTO, а в качестве аргументов принимают примитивные типы и набор изменений (которые накопились в данном ClientContext-е). Структура набора изменений наружу никак не видна, изменения просто применяются к серверным объектам.
>обеспечением конкурентнго доступа
да, это непростой вопрос, но все же его можно попытаться решить отдельно, не затрагивая преимущества Unit of work-а на клиенте.
Re[14]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, samius, Вы писали:
>Вас интересует подгрузка нового продукта и внедрение его в старый граф?
Меня интересует полный цикл работы с Lookup-ом (желательно описать все подробно).
1. на форме у каждого OrderItem-ам надо отображать Description продукта. Description продукта находится в том же DataSet-е или в отдельном? и в какой DataTable?
2. что происходит при смене продукта у одно OrderItem-а?
>Вообще, на сколько мне известно, DTO не предполагает сценариев докачки данных. Обычно, с сервера заказываются новые DTO графы, которые полностью подменяют старые. На мой взгляд смешивание графов объектов, полученных с разных сессий, может привести к непредсказуемым последствиям и трудноотлаживаемым багам. Хотя, Merge датасетов не исключен.
Вы предлагаете гонять весь заказ на сервер и обратно, когда меняется продукт у одного из OrderItem-ов?
Re[15]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, samius, Вы писали:
>>Вас интересует подгрузка нового продукта и внедрение его в старый граф? AP>Меня интересует полный цикл работы с Lookup-ом (желательно описать все подробно).
Я толком проблему не представляю, но все же попробую ниже. AP>1. на форме у каждого OrderItem-ам надо отображать Description продукта. Description продукта находится в том же DataSet-е или в отдельном? и в какой DataTable?
DataSet обязан быть тот же. Таблица — не принципиально, если есть в датасете relation. AP>2. что происходит при смене продукта у одно OrderItem-а?
Ну вот тут и попробую. Проблема в том, что при редактировании требуется подгрузка данных, и не ясно, как ее реализовать в случае с реализацией на DataSet? Проблема не только DataSet-а. Если брать общий случай, то даже с графами объектов потребуется заказывать новый граф с сервера, потом извлекать из графа объекты и вставлять их в существующий граф. Случай с OrderItem и Product, возможно этого не подразумевает (я не знаю ваших таблиц и связей), но общий случай требует мержа (кстати, в общем случае связей может быть много). Работать с двумя графами объектов, как и с двумя DataSet-ами, не удобно, это может привести к невнятному коду и ошибкам на пустом месте.
Как вариант, можно запросить на сервере новый DTO, который содержит все нужные данные (и те что есть, и те что надо подгрузить). Но как быть, если в существующем DTO уже начались изменения? Отправлять его на сервер в незаконченном виде нельзя.
В таком случае есть, как минимум, два решения:
1) Пойти в сторону UnitOfWork и учиться мержить графы объектов, либо низлежащие DataSet-ы. И то и другое требует сноровки, не исключены конфликты.
2) Разделить бизнес-объекты и DTO. Точнее даже не разделить их (они и так должны быть разделены), а не сливать их вместе ради экономии (Persistence Ignorance, устаревшее POCO, оригинал — POJO). Тогда логика клиента, оперируя PI объектами не будет влиять на переданные с сервера DTO никаким образом ровно до того момента, когда придет время зафиксировать операцию. Тогда PI объекты можно будет отобразить в DTO графе и отправить на сервер изменения (в Вашем случае историю изменения DTO при отображении в него изменений в PI объектах). Я полагаю, что именно это и есть чистое использование DTO. Здесь он не смешивается с UOW. Еще раз повторюсь, DTO не будет принимать никаких изменений до тех пор, пока не потребуется зафиксировать операцию. Соответсвтенно, его можно безопасно мержить (в случае с DataSet встроенными методами), не боясь конфликтов, можно выкидывать и заменять новым... Можно перед совершением отображения PI2DTO заказать последний DTO с сервера для анализа на конфликты (теперь угроза конфликтов появится здесь).
Это потребует отделения логики от персистентности и транспортировки. Готовы Вы пойти на это? Палка о двух концах. Оно вроде бы так и надо, но там как правило всплыавет ворох проблемм. Зато все тестируется на ура.
Вообще, еще принято отделять объекты представления от бизнес-модели. Т.е. у представления, бизнес-модели и DataServices (для двухзвенки) свои собственные типы, которые друг на друга маппируются. В случае со Smart client-ом тут как раз зависит от места проведения бизнес-операций. Похоже, что в Вашем случае, бизнес операция проводится на клиенте, а сервером только фиксируется, т.е. получился удаленный DAL. А значит место DTO объектов между объектами бизнес-модели и удаленным DAL-ом.
Все это хорошо, но эта полная схема может оправдать себя наверное только в больших проектах с большим периодом разработки и с долгой поддержкой. К сожалению, я только читал об этом {здесь}, сам не принимал участие в разработках с таким подходом, но очень бы хотелось. В книге выявлено очень много граблей на этом пути, как всегда, никаких универсальных рецептов, сплошные компромиссы.
>>Вообще, на сколько мне известно, DTO не предполагает сценариев докачки данных. Обычно, с сервера заказываются новые DTO графы, которые полностью подменяют старые. На мой взгляд смешивание графов объектов, полученных с разных сессий, может привести к непредсказуемым последствиям и трудноотлаживаемым багам. Хотя, Merge датасетов не исключен. AP>Вы предлагаете гонять весь заказ на сервер и обратно, когда меняется продукт у одного из OrderItem-ов?
Нет, гонять туда его нельзя. Разве что если предусмотреть бизнес-операцию "Сохранить черновик заказа". Но это не выход, выдумывать промежуточные состояния системы каждый раз когда появляется угроза слить недосформированные данные.
Re[16]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, samius, Вы писали:
>Я толком проблему не представляю, но все же попробую ниже. >(я не знаю ваших таблиц и связей)
Каких конкретно знаний вам не хватает?
>Если брать общий случай
Давайте пока разберемся с конкретным случаем Lookup-а. а потом попытаемся обобщить.
> Как вариант, можно запросить на сервере новый DTO, который содержит все нужные данные (и те что есть, и те что надо подгрузить). Но как быть, если в существующем DTO уже начались изменения? Отправлять его на сервер в незаконченном виде нельзя.
Почему же нельзя? Отправить DTO на сервер, на сервере перелить данные из DTO в серверные объекты, провести необходимое действие (выставить новый Product) с серверными объектами (на сервере и Identity Map есть, и Lazy loading). В базу не сограняем. Выгрузить новый граф DTO. Это вариант идеологически совпадает с обычными Web Forms (там данные передаются в HTML-е, а в нашем случае в виде графа DTO). Но гоняются данные всей формы при смене значения в одном Lookup поле. Получаем вопрос, насколько критично так часто гонять весь заказ на сервер?
>В случае со Smart client-ом тут как раз зависит от места проведения бизнес-операций. Похоже, что в Вашем случае, бизнес операция проводится на клиенте, а сервером только фиксируется, т.е. получился удаленный DAL.
Нет, нет, я не собираюсь все расчеты и другие операции (workflow и т.д.) проводить на клиенте. Но проводить все операции (в первую очередь расчеты) на сервере тоже не выход, пользователь будет сильно удивлен, если для расчета количества дней между двумя датами идет удаленный вызов, и пользователь вынужден ждать (удаленные вызовы это почти всегда заметная задержка). С другой стороны для части расчетов требуются данные (из справочников), которые находятся в базе, а на клиенте их нет. Причем какие именно данные зависит от того, по какой ветке пойдет расчет, т.е. от конкретных значений полей, в таком сценарии удобна Lazy Loading. Устраивать Lazy Loading через Интернет это плохо скажется на производительности.
Таким образом, удобный для пользователя вариант:
1. Расчеты, не требующие большого количества данных из базы, выполнять на клиенте.
2. Расчеты, требующие большого количества данных, выполнять на сервере. Также на сервере выполняется движение по workflow, подготовка регистров для отчетов и т.д.
> Можно перед совершением отображения PI2DTO заказать последний DTO с сервера для анализа на конфликты (теперь угроза конфликтов появится здесь).
Какую проблему решает перенос проблемной ситуации с сервера на клиента?
> Тогда логика клиента, оперируя PI объектами не будет влиять на переданные с сервера DTO никаким образом ровно до того момента, когда придет время зафиксировать операцию.
Описанный вами сценарии работы с данными безотносительно в каких объектах они находятся:
1. Данные пришли с сервера
2. Пользователь поработал с пришедшими данными
3. Данные (изменения) отправились на сервер
Имхо, на этот сценарий и ориентированны DataSet-ы.
Но на самом деле общий сценарий сложнее
1. Пользователь запросил первую порцию данных
2. Посмотрел, поизменял данные
3. Затребовал вторую порцию данных.
4. Посмотрел на обе порции данных вместе. Поизменял первую порцию.
5. Повторил шаги 3-4 для следующей порции данных
6. Данные отправились на сервер
В совсем общем сценарии на 4-ом шаге пользователь может менять данные из второй порции. Но очень часто бизнес укладывается в описанную схему (основной момент -- данные из второй порции только для чтения). На мой взгляд инфраструктура для описанной схемы сейчас востребована.
Если вернуться к Order-ам -- Product в OrderItem-е только для чтения, поэтому я не вижу ничего плохо в том, что на клиенте могут оказаться две копии одного продукта. Скорее всего можно мириться даже с ситуацией, когда кто-то сумел поменять продукт, и у нас на клиенте в разных местах формы отображается две разные версии одного продукта. Хотите увидеть полностью актуальные данные сохраняйте и обновляйте всю форму. Поскольку ситуация редкая, а гонять данный каждый раз тоже плохо, то с этим можно мириться, имхо.
Re[17]: Покритикуйте архитектуру обмена данными для smart кл
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, samius, Вы писали:
>>Я толком проблему не представляю, но все же попробую ниже. >>(я не знаю ваших таблиц и связей) AP>Каких конкретно знаний вам не хватает?
Я не знаю, будет ли Product единственным объектом, подгружаемым с сервера по изменению Lookup-а. Если подгружаемый Pruduct один, то мерж будет тривиальный. Если Product связан с другими сущностями, которые могут быть уже подкачаны на клиент, то мерж усложняется, не зависимо от основы DTO (DataSet или граф объектов).
>> Как вариант, можно запросить на сервере новый DTO, который содержит все нужные данные (и те что есть, и те что надо подгрузить). Но как быть, если в существующем DTO уже начались изменения? Отправлять его на сервер в незаконченном виде нельзя. AP>Почему же нельзя?
Отправлять для сохранения нельзя, если мержить — то можно, но по сути переносить на сервер мерж нет смысла. AP>Отправить DTO на сервер, на сервере перелить данные из DTO в серверные объекты, провести необходимое действие (выставить новый Product) с серверными объектами (на сервере и Identity Map есть, и Lazy loading).
Если на сервере есть Identity Map, почему ее не может быть на клиенте, раз она все равно есть. AP>В базу не сограняем. Выгрузить новый граф DTO. Это вариант идеологически совпадает с обычными Web Forms (там данные передаются в HTML-е, а в нашем случае в виде графа DTO).
Все же есть грань между ViewState и объектами модели. AP>Но гоняются данные всей формы при смене значения в одном Lookup поле. Получаем вопрос, насколько критично так часто гонять весь заказ на сервер?
Только Вы можете ответить, на сколько критично это будет для Вашего проекта. А так же на вопрос "зачем гонять данные всей формы?", и "что из данных формы можно не гонять?".
AP>...Таким образом, удобный для пользователя вариант: AP>1. Расчеты, не требующие большого количества данных из базы, выполнять на клиенте. AP>2. Расчеты, требующие большого количества данных, выполнять на сервере. Также на сервере выполняется движение по workflow, подготовка регистров для отчетов и т.д.
Если здесь значимы только нефункциональные требования, то вполне логично.
>> Можно перед совершением отображения PI2DTO заказать последний DTO с сервера для анализа на конфликты (теперь угроза конфликтов появится здесь). AP>Какую проблему решает перенос проблемной ситуации с сервера на клиента?
Конкретно для мержа графов и анализа конфликтов — избавляет от необходимости переноса и анализа состояния клиента (в том числе невалидного промежуточного) на сервер.
>> Тогда логика клиента, оперируя PI объектами не будет влиять на переданные с сервера DTO никаким образом ровно до того момента, когда придет время зафиксировать операцию. AP>Описанный вами сценарии работы с данными безотносительно в каких объектах они находятся: AP>1. Данные пришли с сервера AP>2. Пользователь поработал с пришедшими данными AP>3. Данные (изменения) отправились на сервер AP>Имхо, на этот сценарий и ориентированны DataSet-ы. AP>Но на самом деле общий сценарий сложнее AP>1. Пользователь запросил первую порцию данных AP>2. Посмотрел, поизменял данные AP>3. Затребовал вторую порцию данных. AP>4. Посмотрел на обе порции данных вместе. Поизменял первую порцию. AP>5. Повторил шаги 3-4 для следующей порции данных AP>6. Данные отправились на сервер
Если не бояться конфликтов и перемешиваний объектов разных графов, что может повлечь передачу нескольких графов на сервер вместо одного, то можно использовать одновременно неограниченное кол-во порций. Имхо, это игра в наперстки с самим собой.
AP>Если вернуться к Order-ам -- Product в OrderItem-е только для чтения, поэтому я не вижу ничего плохо в том, что на клиенте могут оказаться две копии одного продукта. Скорее всего можно мириться даже с ситуацией, когда кто-то сумел поменять продукт, и у нас на клиенте в разных местах формы отображается две разные версии одного продукта. Хотите увидеть полностью актуальные данные сохраняйте и обновляйте всю форму. Поскольку ситуация редкая, а гонять данный каждый раз тоже плохо, то с этим можно мириться, имхо.
Представьте ситуацию: расплачиваясь за покупку, Вы видите одну сумму, но с карточки снимается другая сумма. Либо Вы покупаете один товар, но доставляют другой... Неприятно, но по замыслу разработчика виноваты именно Вы! Надо было чаще обновлять форму!
Да, можно с этим мириться или нет, решать будет разработчик (если в функциональных требованиях это не оговорено). Только в основе решения должно лежать не то, что плохо лишний раз гонять данные, а то, в каком положении может оказаться пользователь системы.