Я борюсь с этим второй день, и я просто сыт по горло.
Я получаю странные исключения, связанные с моим пользовательским интерфейсом.
Перво-наперво.Моя модель выглядит примерно так:
Базовый класс:
public class DbItem: ObservableModel
{
public virtual Document ParentDocument { get; set; }
Guid id;
public virtual Guid Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
NotifyPropertyChanged();
}
}
}
string name = string.Empty;
public virtual string Name
{
get { return name; }
set
{
if (value == null || name != value)
{
name = value;
NotifyPropertyChanged();
}
}
}
}
Далее у нас есть класс PeriodBase:
public enum PeriodType
{
Year,
Sheet
}
public abstract class PeriodBase : DbItem
{
public virtual Period ParentPeriod { get; set; }
public virtual PeriodType PeriodType { get; set; }
}
Есть еще несколько свойств, но я просто удалилих здесь для ясности.
Далее, у нас есть класс Period, который наследуется от PeriodBase:
public class Period : PeriodBase
{
IList<PeriodBase> periods = new ObservableCollection<PeriodBase>();
public virtual IList<PeriodBase> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
Теперь у Period могут быть другие периоды и листы (которые также наследуются от PeriodBase):
public class Sheet : PeriodBase
{
DateTimeOffset startDate;
public override DateTimeOffset StartDate
{
get { return startDate; }
set
{
if (startDate != value)
{
startDate = value;
NotifyPropertyChanged();
}
}
}
DateTimeOffset endDate;
public override DateTimeOffset EndDate
{
get { return endDate; }
set
{
if (endDate != value)
{
endDate = value;
NotifyPropertyChanged();
}
}
}
}
И, наконец, у нас есть класс документа, который состоит из периодов:
public class Document: DbItem
{
IList<Period> periods = new ObservableCollection<Period>();
public virtual IList<Period> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
Как вы можете догадаться, я получаю древовидную иерархию, подобную этой:
- Document
- Period 1
- Sheet 1
Мои привязки выглядят так:
public class DocumentMap : DbItemMap<Document>
{
public DocumentMap()
{
Table("documents");
HasMany(x => x.Periods).ForeignKeyConstraintName("ParentDocument_id");
}
}
public class PeriodBaseMap: DbItemMap<PeriodBase>
{
public PeriodBaseMap()
{
UseUnionSubclassForInheritanceMapping();
References(x => x.ParentPeriod);
Map(x => x.Name).Not.Nullable();
Map(x => x.PeriodType).CustomType<PeriodType>();
}
}
public class PeriodMap : SubclassMap<Period>
{
public PeriodMap()
{
Table("periods");
Abstract();
References(x => x.ParentDocument);
HasMany(x => x.Periods).Inverse().Not.LazyLoad();
}
}
public class SheetMap : SubclassMap<Sheet>
{
public SheetMap()
{
Table("sheets");
Abstract();
Map(x => x.StartDate);
Map(x => x.EndDate);
}
}
На данный момент я просто загружаюсь везде.Просто для простоты.
Теперь WPF.Вот как я создаю свой TreeView (я использую элементы управления syncfusion):
<sf:TreeViewAdv>
<sf:TreeViewItemAdv
Header="Document"
LeftImageSource="../Resources/database.png"
ItemsSource="{Binding Periods}"
IsExpanded="True"
>
<sf:TreeViewItemAdv.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Periods}"> <!-- Period -->
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/> <!-- Sheet -->
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</sf:TreeViewItemAdv.ItemTemplate>
</sf:TreeViewItemAdv>
</sf:TreeViewAdv>
И все работает, пока я не сохраню записи.Это просто SaveAsync в одной транзакции.
Все сохраняется, но затем я получаю странную ошибку.Приложение аварийно завершает работу с сообщением: не удается привести TreeViewItemAdv к PeriodBase.
Какого черта?Я даже не могу найти место, когда оно действительно бросает.Это трассировка стека из информации об исключении:
in NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.IList.IndexOf(Object value)
in System.Windows.Data.ListCollectionView.InternalIndexOf(Object item)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.Initialize(FrameworkTemplate template)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.TreeViewItemAdv_Loaded(Object sender, RoutedEventArgs e)
in System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
in System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
in System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
in System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
in MS.Internal.LoadedOrUnloadedOperation.DoWork()
in System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
in System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
in System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
in System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
in System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
in System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
Что важно, я получаю ту же ошибку после запуска приложения, загрузки документа и щелчка на расширителе в древовидном представлении, чтобы развернуть Period.Но все работает нормально, когда я запускаю приложение в первый раз, пока я не сохраню документ.
В чем может быть проблема?
В ответ на сообщение Марка Фельдмана
Я решил ответить в ответ, так как это слишком долго, чтобы комментировать.Это моя первая встреча с ORM, поэтому у меня могут быть некоторые неправильные мысли по этому поводу.У меня есть только одна модель в моем решении.Обычно (с использованием SQL) это будет работать.Я бы взял объект, вставил его в БД и другим способом.
Так что я сделал то же самое здесь.У меня просто есть одна бизнес-модель, в которой есть несколько простых бизнес-правил.Он используется во ViewModels и хранится в БД.Это плохое решение?Должен ли я иметь другую модель и несколько нарушить принцип СУХОГО?
В моей голове предполагалось работать так: Пользователь нажимает «Создать новый лист».Вот вы (это часть моего метода ViewModel ->, который вызывается из команды):
void CreateNewSheetInActiveDocument()
{
Sheet sh = ActiveDocument.CreateItem<Sheet>();
ActiveDocument.LastPeriod.Periods.Add(sh);
}
Это больше похоже на псевдокод, но оно сохраняет идею.Активный документ создает мой лист.Это сделано потому, что документ подписывается на событие PropertyChanged просто для того, чтобы узнать, было ли оно изменено.Periods - это ObservableCollection, так что я могу реагировать на добавление и удаление элементов.Благодаря этому периоду можно установить parentPeriod для моего листа автоматически.
И затем пользователь сохраняет его в db:
async Task SaveDocument(Document doc)
{
foreach(var item in doc.ModifiedItems)
db.SaveOrUpdate(item);
}
ModifiedItems - это просто словарь, в котором хранятся элементы, которые были изменены.Благодаря этому мне не нужно сохранять весь документ, только измененные элементы.
Так что, насколько я понимаю, это не так, как должно быть.Итак, что будет правильный способ сделать это?Или, может быть, ORM здесь не подходит?