Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.99/116: Рейтинг темы: голосов - 116, средняя оценка - 4.99
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2

INPC (INotifyPropertyChanged) и получение данных из Модели [WPF, Элд Хасп]

27.01.2019, 17:51. Показов 24944. Ответов 63

Лучший ответ Сообщение было отмечено Элд Хасп как решение

Решение

Студворк — интернет-сервис помощи студентам
Тема из цикла Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп]

MVVM состоит из трёх раздельных частей. "Знания" этих частей друг о другу ограничены. View знает только о VM, VM только Model, а Model знать никого не желает.

В такой схеме передача данных от View в Model проблем не вызывает. Ограничения есть только для передачи данных от View к VM. Для отправки данных View обращается к Set методу нужного свойства VM (к которому осуществлена привязка), поэтому привязывать свойства View можно только к публичным свойствам VM. Привязать к полям или методам VM невозможно.

Если взять VM из примера в WPF команды и MVVM. Часть 1. пост#15, то свойства First, Second можно упростить до автосвойств и на функционировании приложения это ни как не скажется (сброс свойства Result в данном случае можно опустить)
C#
9
10
public int First { get; set; }
public int Second { get; set; }
Получение же данных происходит иным образом. Так как у VM нет знаний о View, она сама не может "лезть" в View и менять там значения свойств. View сама запрашивает у VM нужные данные вызывая метод Get нужного свойства VM.

Но как определить View, что данные изменились и их надо заново запросить? Для этого VM должна известить View о том какие данные были изменены. Это извещение (событие) для WPF View должно происходить через реализацию интерфейсов INotifyPropertyChanged (INPC) для свойств и INotifyCollectionChanged (INCC) для коллекций (здесь рассматривать не буду).

Дефолтная реализация реализация интерфейса INPC состоит всего из одной строчки
C#
1
public event PropertyChangedEventHandler PropertyChanged;
Но для удобства использования обычно вводят ещё и метод для вызова этого события
C#
1
2
3
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName="") 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Атрибут CallerMemberName введён с Framework 4.5. Он позволяет не указывать имя свойства, если вызов происходит из Set метода этого свойства. В указанном примере это используется в свойстве Result
C#
1
2
3
4
5
6
7
8
9
        public int? Result
        {
            get => _result;
            private set
            {
                _result = value;
                OnPropertyChanged();
            }
        }
Использование без параметра OnPropertyChanged() равносильно OnPropertyChanged("Result")

Но только такое использование INPC необязательно. Можно заменить Result на автосвойство и вызывать OnPropertyChanged при записи в него нового значения.
C#
1
2
3
4
5
6
7
8
9
        public int? Result { get; private set;  }
        private void OnCalculate(object parameter)
        {
            if (parameter is OperatorsEnum Operator)
                Result = model.Calculate(First, Operator, Second);
            else
                Result = null;
            OnPropertyChanged("Result");
        }
В указанном в WPF команды и MVVM. Часть 1. пост#15 примере кнопка по сути не нужна. Она была введена для демонстрации команд. Изменим пример - убрав кнопку и используя INPC для обновления Result.

В VM при изменении значений свойств First, Second и SelectOperator будем отправлять в View сообщение об изменении свойства
Result. А в свойстве Result пропишем обращение к свойствам Модели
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    /// <summary>Класс Модели Представления</summary>
    public class CalcViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #region Секция свойств 
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
 
        public int First
        {
            get => _first;
            set
            {
                if (_first != value)
                {
                    _first = value;
                    OnPropertyChanged("Result");
                }
            }
        }
        public int Second
        {
            get => _second;
            set
            {
                if (_second != value)
                {
                    _second = value;
                    OnPropertyChanged("Result");
                }
            }
        }
 
        public OperatorsEnum SelectOperator
        {
            get => _selectOperator;
            set
            {
                _selectOperator = value;
                OnPropertyChanged("Result");
            }
        }
 
        public int? Result => model.IsCanExecuteCalculate(First, SelectOperator, Second)
            ? model.Calculate(First, SelectOperator, Second)
            : null;
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
        CalcModel model = new CalcModel(); // Ссылка на модель
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (Calc)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>
Рассмотрим теперь полную схему получения данных в MVVM

Если для отправки извещения в View, VM должна реализовывать INPC или INCC, то модель этим не ограничена. Но использование этих интерфейсов в большинстве случаев удобно и позволяет сделать код более прозрачным и читаемым. Можно использовать INPC не совсем стандартно. По своей сути событие PropertyChanged пересылает просто текстовое сообщение (string), ни каких проверок на существование свойства с таким именем не производится. Поэтому не обязательно чтобы параметр метода OnPropertyChanged был названием свойства.
В примере выше, VM обращается к методам Модели сразу возвращающим данные, но такие методы не всегда есть у модели. И если значения свойств First, Second и SelectOperator сразу посылать в Модель, то VM не может знать изменится Result или нет. Для этого при изменении Result Модель должна создать событие извещающее об этом, а VM "прослушивать" это событие и по нему запросить данные.

Пример Модели. В Модели реализованы методы для получения данных от VM: First, Second и SelectOperator. Методы для возвращения данных в VM: GetResult и GetOperators. Для удобства введён дополнительный конструктор сразу подключающий "прослушку" события PropertyChanged.
Имена методов Модели намеренно сделаны не совпадающими с именами свойств VM для большей наглядности взаимодействия. Для этой же цели PropertyChanged сообщает VM об изменении несуществующего свойства ResultChange
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    /// <summary>Класс модели</summary>
    public class CalcINPCModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
 
        #region закрытые поля
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
        private int? _result = null;
        #endregion
 
        #region Методы получающие данные
        public void SetFirst(int value)
        {
            if (_first != value)
            {
                _first = value;
                OnResult();
            }
        }
        public void SetSecond(int value)
        {
            if (_second != value)
            {
                _second = value;
                OnResult();
            }
        }
 
        public void SetOperator(OperatorsEnum value)
        {
            if (_selectOperator != value)
            {
                _selectOperator = value;
                OnResult();
            }
        }
        #endregion
 
        #region Методы отправляющие данные
        public int? GetResult() => _result;
 
        /// <summary>Возвращает последовательность значений перечисления OperatorsEnum</summary>
        /// <returns></returns>
        public IEnumerable<OperatorsEnum> GetOperators()
            => Enum.GetValues(typeof(OperatorsEnum)).Cast<OperatorsEnum>();
        #endregion
 
        /// <summary>Обработка полученных данных</summary>
        private void OnResult()
        {
            int? value = null;
            if (_selectOperator != OperatorsEnum.Деление || _second != 0)
                switch (_selectOperator)
                {
                    case OperatorsEnum.Сложение: value = _first + _second; break;
                    case OperatorsEnum.Умножение: value = _first * _second; break;
                    case OperatorsEnum.Вычитание: value = _first - _second; break;
                    case OperatorsEnum.Деление: value = _first / _second; break;
                }
            if (_result != value)
            {
                _result = value;
                OnPropertyChanged("ResultChange");
            }
        }
 
        /// <summary>Безпараметрический конструктор</summary>
        public CalcINPCModel() { }
        /// <summary>Конструктор принимающий делегат для "прослушивания" события PropertyChanged</summary>
        /// <param name="propertyChanged">Делегат для прослушки</param>
        public CalcINPCModel(PropertyChangedEventHandler propertyChanged)
            => PropertyChanged += propertyChanged;
    }
В ViewModel свойства First, Second и SelectOperator ни где не хранят свои значения. При получении изменений от View они сразу отправляют их в методы Модели.
Инициализация Модели сделана через обращение к свойству для подключения прослушки чтобы не создавать конструктор ViewModel. В методе Model_PropertyChanged прослушивающем PropertyChanged проверяется имя свойства, если оно ResultChange, то отправляется извещение в View об изменении свойства Result. Пр обращении View к свойству Result оно получает своё значение от модели.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    /// <summary>Класс Модели Представления</summary>
    public class CalcINPCViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #region Секция свойств 
        public int First { set => model.SetFirst(value); }
        public int Second { set => model.SetSecond(value); }
 
        public OperatorsEnum SelectOperator { set => model.SetOperator(value); }
 
        public int? Result => model.GetResult();
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
        CalcINPCModel _model;
        CalcINPCModel model => _model ?? (_model = new CalcINPCModel(Model_PropertyChanged)); // Ссылка на модель
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
    }
XAML окна не изменился, но на всякий случай привожу его
Кликните здесь для просмотра всего текста
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcINPCWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (CalcINPC)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcINPCViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>


Теперь к такой Модели можно подключить другие View и ViewModel

Модель и первые View+VM почти те же. Добавлена только возможность ссылки на модель через статическое поле.
Добавление в Модели
C#
1
2
        static CalcIWithResultModel _model;
        public static CalcIWithResultModel Model => _model ?? (_model = new CalcIWithResultModel());
Получение Модели в конструкторе VM
C#
1
2
3
4
5
6
        readonly CalcIWithResultModel model;
        public CalcIWithResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
Целиком коды цепочки "Calc"
Кликните здесь для просмотра всего текста
Модель
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    /// <summary>Класс модели</summary>
    public class CalcIWithResultModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        static CalcIWithResultModel _model;
        public static CalcIWithResultModel Model => _model ?? (_model = new CalcIWithResultModel());
 
        #region закрытые поля
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
        private int? _result = null;
        #endregion
 
        #region Методы получающие данные
        public void SetFirst(int value)
        {
            if (_first != value)
            {
                _first = value;
                OnResult();
            }
        }
        public void SetSecond(int value)
        {
            if (_second != value)
            {
                _second = value;
                OnResult();
            }
        }
 
        public void SetOperator(OperatorsEnum value)
        {
            if (_selectOperator != value)
            {
                _selectOperator = value;
                OnResult();
            }
        }
        #endregion
 
        #region Методы отправляющие данные
        public int? GetResult() => _result;
 
        /// <summary>Возвращает последовательность значений перечисления OperatorsEnum</summary>
        /// <returns></returns>
        public IEnumerable<OperatorsEnum> GetOperators()
            => Enum.GetValues(typeof(OperatorsEnum)).Cast<OperatorsEnum>();
        #endregion
 
        /// <summary>Обработка полученных данных</summary>
        private void OnResult()
        {
            int? value = null;
            if (_selectOperator != OperatorsEnum.Деление || _second != 0)
                switch (_selectOperator)
                {
                    case OperatorsEnum.Сложение: value = _first + _second; break;
                    case OperatorsEnum.Умножение: value = _first * _second; break;
                    case OperatorsEnum.Вычитание: value = _first - _second; break;
                    case OperatorsEnum.Деление: value = _first / _second; break;
                }
            if (_result != value)
            {
                _result = value;
                OnPropertyChanged("ResultChange");
            }
        }
 
        /// <summary>Безпараметрический конструктор</summary>
        public CalcIWithResultModel() { }
        /// <summary>Конструктор принимающий делегат для "прослушивания" события PropertyChanged</summary>
        /// <param name="propertyChanged">Делегат для прослушки</param>
    }
ViewModel
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    /// <summary>Класс Модели Представления</summary>
    public class CalcIWithResultViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        #region Секция свойств 
        public int First { set => model.SetFirst(value); }
        public int Second { set => model.SetSecond(value); }
 
        public OperatorsEnum SelectOperator { set => model.SetOperator(value); }
 
        public int? Result => model.GetResult();
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
 
        readonly CalcIWithResultModel model;
        public CalcIWithResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
    }
Окно
Кликните здесь для просмотра всего текста
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcIWithResultWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (CalcIWithResult)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcIWithResultViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>

Во второй цепочке "Result" используется упрощённая VM и выводится только результат.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    /// <summary>Класс упрощённой ViewModel</summary>
    public class ResultViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        public int? Result => model.GetResult();
 
        readonly CalcIWithResultModel model;
        public ResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
 
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
 
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Window x:Class="INPCinModel.ResultWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="ResultWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ResultViewModel/>
    </Window.DataContext>
    <Viewbox>
        <TextBlock Text="{Binding Result}"/>
    </Viewbox>
</Window>
И, конечно, для показа обоих окон надо добавить в App обработчик Startup
C#
1
2
3
4
5
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            (new CalcIWithResultWindow()).Show();
            (new ResultWindow()).Show();
        }
Архив с кодами приложен.
Вложения
Тип файла: zip INPCinModel.zip (100.7 Кб, 256 просмотров)
10
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
27.01.2019, 17:51
Ответы с готовыми решениями:

Библиотека элементов для реализации WPF MVVM Решений [WPF, Элд Хасп]
Решил собрать элементы используемые в темах в этом разделе. В библиотеку включаю элементы которые, на мой взгляд, имеют универсальное...

WPF команды и MVVM. Часть 1. [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Для использования и создания WPF команд в Net предусмотрен...

Передача данных между Окнами, между VM, Шина Сообщений, Локатор [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Использование статического класса. Передача...

63
 Аватар для werymag
27 / 12 / 1
Регистрация: 20.05.2015
Сообщений: 217
01.08.2023, 18:13
Студворк — интернет-сервис помощи студентам
Ясно, в целом так и представлял себе. Ещё в рамках разговора интересно понимание термина "бизнес логика" и что за логика должна быть в модели.

Ну на примере того же "калькулятора" который эксплуатируют в этих темах в качестве примера модели.
Вычисление значений чисел - это бизнес логика, или же это можно отнести к логике VM?
Если же это логика модели, то логика, например извлечения и записи(и верификации) в БД будут находится на этом же "уровне" модели.

Просто я как-то для себя разделял так, что:
View - то, что связанно с визуализацией данных (в том числе необходимая для визуализации логика, которая не требует обращения к VM, вроде верификации вводимого значения).
ViewModel - обработка событий View плюс вся обработка данных, будь то математика, преобразования, сортировка, аналитика...
Model - Всё что касается верификации, доступа, хранения данных.

Насколько это правильное деление, или же каждый сам для себя решает, что для него бизнес логика и где он хочет сделать ещё один слой абстракции.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
01.08.2023, 20:24  [ТС]
Цитата Сообщение от werymag Посмотреть сообщение
View - то, что связанно с визуализацией данных
Да.

Цитата Сообщение от werymag Посмотреть сообщение
ViewModel - обработка событий View
НЕ-Е-Е-Е-ЕТ!!!
Единственная функция ViewModel - это отражение Модели в удобном для View виде. В случае WPF - отражение через свои свойства.
По сути VM - это просто прокси для удобного доступа к Модели.
НИКАКИT события View в VM ни при каких условиях обрабатываться не могут.

А вот события VM (и другие члены VM) в View используются.

Цитата Сообщение от werymag Посмотреть сообщение
ViewModel - .... плюс вся обработка данных, будь то математика, преобразования, сортировка, аналитика...
Смотря какая обработка.
Например, конвертация типов Модели в удобные для для View типы - да.
Математика простейшей верификации (не пустая строка, число из заданного диапазона и т.п.) - да.
Сортировка.... под вопросом. Если коллекция загружена целиком в память, то проще и правильнее её сортировать в View. Если нужна сортированная порция данных, то это функция Модели. Мне даже в голову не приходит в каких задачах сортировку правильно будет реализовать в VM. Но не исключаю, что такие редкие задачи существуют.
Аналитика...?? А это что такое? Что вы под этим подразумеваете?

Цитата Сообщение от werymag Посмотреть сообщение
Model - Всё что касается верификации, доступа, хранения данных.
Модель - это некая абстрактная часть приложения осуществляющее всю логическую обработку данных, которая не зависит от Представления, его типа и даже его наличия-отсутсвия.
0
 Аватар для werymag
27 / 12 / 1
Регистрация: 20.05.2015
Сообщений: 217
01.08.2023, 21:53
Цитата Сообщение от Элд Хасп Посмотреть сообщение
НЕ-Е-Е-Е-ЕТ!!!
Единственная функция ViewModel - это отражение Модели в удобном для View виде. В случае WPF - отражение через свои свойства.
По сути VM - это просто прокси для удобного доступа к Модели.
НИКАКИT события View в VM ни при каких условиях обрабатываться не могут.
События это условно, ну например, есть кнопка "получить результат сложения" (в реальности что-то сложное, вплоть до загрузки данных и обработки данных из БД), та через IComand вызывает соответствующий метод во VM, а тот уже выполняет необходимые действия. (технически в View происходит событие Click, которые через патерн Команда вызывает соответствующий метод с параметром в VM) Не совсем понимаю, как это можно иначе реализовать.
И таких действий, когда при событиях в View(кликах, выделениях, перемещениях) должны выполнятся действия именно с данными в VM (и в model) может быть множество, что это если не обработка событий в конечном итоге?
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Математика простейшей верификации (не пустая строка, число из заданного диапазона и т.п.) - да.
Ну я выше как раз спрашивал, пусть будет - сложить два числа. Ну или примеры посложнее ниже.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Сортировка.... под вопросом. Если коллекция загружена целиком в память, то проще и правильнее её сортировать в View. Если нужна сортированная порция данных, то это функция Модели. Мне даже в голову не приходит в каких задачах сортировку правильно будет реализовать в VM. Но не исключаю, что такие редкие задачи существуют.
Ну пусть будет не просто сортировка, а поиск по множеству параметров, и в финальную выборку будут вовлечены множество компонентов View, и пусть там будет прям логика-логика с вычислением чего-нибудь. Пример из жизни - выбрать из списка трубы способные выдержать определенную нагрузку, при том на входе пусть будут только давление и модуль упругости, а при выборке надо рассчитать усилие, деформацию, сравнить с критерием и т.п., так же внутри внутри формул входит куча коэффициентов и параметров (в реальности всё раз в 100 сложнее).
Делать это в View? (ведь формально это чисто отображение данных) Но как? В Хамл с логикой не разгуляешься, DP и Behavior для логики не подходят. КодБихайн, но это же тоже плохо, да и банально неудобно туда передавать данные.
Делать это в Model? Но по факту же это просто выборка, прямой обработки и преобразования данных нет, чисто действия необходимые для отображения и принятия решения.

Или ещё пример - из одной коллекции (имеющей отображение в View) надо добавить элемент в другую (тоже имеющие отображение в View), при том по хитрой и сложной логике (с математикой, сортировкой, аналитикой и прочим упомянутым, даже объекты на самом деле разные и их надо преобразовать по некоей логике) .

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Аналитика...?? А это что такое? Что вы под этим подразумеваете?
Выборка и преобразование данных, ну как в примерах выше.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
01.08.2023, 23:40  [ТС]
Цитата Сообщение от werymag Посмотреть сообщение
(технически в View происходит событие Click, которые через патерн Команда вызывает соответствующий метод с параметром в VM) Не совсем понимаю, как это можно иначе реализовать.
Очень просто: Событие (Click) отрабатывается в View; обработчик обращается к VM (свойству, методу - неважно).
В данном случае, исполнение команды - это просто интегрированный в WPF обработчик:
C#
1
2
if(Command?.CanExecute(CommandParameter) == true)
    Command.Execute(CommandParameter);
Команда используются по нескольким причинам:
1) Её можно передать через свойство. А привязка к свойствам это один из основных механизмов WPF;
2) В команде есть члены для проверки её состояния. Что в обобщённом виде очень полезно для многих методов.

Цитата Сообщение от werymag Посмотреть сообщение
Ну я выше как раз спрашивал, пусть будет - сложить два числа.
Для чего?
Если вычислить (например) размер элемента - это должно делать Представления.
Если это обработка данных (например калькулятор), то нужно передать операнды и оператор в Модели и потом дождаться от Модели результата.
В каком случае в VM нужно "для себя" складывать числа?
Наверняка есть такие ситуации (например при конвертации), но ваш пример из-за слишком общего вида очень неудачный.

Цитата Сообщение от werymag Посмотреть сообщение
Ну пусть будет не просто сортировка, а поиск по множеству параметров, и в финальную выборку будут вовлечены множество компонентов View, и пусть там будет прям логика-логика с вычислением чего-нибудь....
Почти на 100% - это задача Модели.

Цитата Сообщение от werymag Посмотреть сообщение
Делать это в Model? Но по факту же это просто выборка, прямой обработки и преобразования данных нет, чисто действия необходимые для отображения и принятия решения.
Зависит от сложности запроса.
Например, создать сложный SQL или LINQ запрос может VM, но исполнять его же будет Модель.
Чем меньше логики в VM - тем чище будет реализация паттерна.

Цитата Сообщение от werymag Посмотреть сообщение
Или ещё пример - из одной коллекции (имеющей отображение в View) надо добавить элемент в другую (тоже имеющие отображение в View)
Это добавление как-то связанно с Данными Модели, влияет на них?
Если нет - то всё должно делаться в View.

Цитата Сообщение от werymag Посмотреть сообщение
Выборка и преобразование данных, ну как в примерах выше.
Примеры выше - неудачные.
Я не вижу в них необходимости участия VM.

Максимум - собрать из нескольких свойств сложный запрос и передать его в Модель.

Добавлено через 3 минуты
Цитата Сообщение от werymag Посмотреть сообщение
Но по факту же это просто выборка, прямой обработки и преобразования данных нет, чисто действия необходимые для отображения и принятия решения.
Попробую с другой стороны показать.
У вас есть Модель и есть к ней разные оболочки: WPF (MVVM), Формы (MVP), Консоль (MVC).
В любом из этих приложений вам нужна "просто выборка".
Как будет правильно сделать: реализовать её три раза в ViewModel, в Presenter и в Controller или один раз в Модели?
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
02.08.2023, 08:35
Цитата Сообщение от werymag Посмотреть сообщение
Пример из жизни - выбрать из списка трубы способные выдержать определенную нагрузку, при том на входе пусть будут только давление и модуль упругости, а при выборке надо рассчитать усилие, деформацию, сравнить с критерием и т.п., так же внутри внутри формул входит куча коэффициентов и параметров (в реальности всё раз в 100 сложнее).
А СУБД с этим справиться не может? Зачем такие расчёты писать в VM и грузить офисный калькулятор? Есть сервер с СУБД - вот пусть и работает, он на это рассчитан и нечего его жалеть... Посчитал и выдал нужные данные, VM их преобразовала в удобоваримый вариант для View и View показал их пользователю. Если уж совсем просто, то VM просто транслирует данные между Model и View, на этом её роль заканчивается...
1
 Аватар для werymag
27 / 12 / 1
Регистрация: 20.05.2015
Сообщений: 217
02.08.2023, 16:02
Начну с конца.
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
А СУБД с этим справиться не может? Зачем такие расчёты писать в VM и грузить офисный калькулятор? Есть сервер с СУБД - вот пусть и работает, он на это рассчитан и нечего его жалеть... Посчитал и выдал нужные данные, VM их преобразовала в удобоваримый вариант для View и View показал их пользователю. Если уж совсем просто, то VM просто транслирует данные между Model и View, на этом её роль заканчивается...
Реальность такова, что как раз "офисный компьютер" - это расчетная WorkStation на ксеоне и с квадро. А сервер - слабый (ибо расчетные сервера крайне дорогая штука). Но к вопросу это никак не относится, так как указанные расчеты быстрые.

В целом если надо сделать выборку из большого числа элементов, которые не нужно загружать, то возможно стоило бы делать на бекэнд сервере, но:
Во первых любой запрос к серверу (в интернет) это почти всегда в на 3-5 порядков дольше, чем любая логика в программе (доли секунды против наносекунд), и это ощутимые ожидания в интерфейсе (не зависания, всё асинхронно, но ожидание ответа), что ну прям очень неприятно для того, что ожидается быстрым.
Во вторых - когда речь не о огромном числе труб, а, допустим о выборке из сотен - то загрузить их все сразу не проблема, да и при том "выборка" это только часть функционала, иногда пользователь просто проскролит до нужной трубы (потому все трубы в любом случае должны быть загружены, подгружать их партиями по мере скрола конечно можно - но не хочу лезть в такие дебри)
В третьих - логика выборки, сложная с куче параметров, передача всех параметров на сервер потребует серилизации, передачи и приёму, десерилизации, то есть сразу куче дополнительной логике.
Думаю тут, как и в ответе ниже скорее речь о переносе логики в отдельный слой, ещё на клиенте, но уже не в VM
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Попробую с другой стороны показать.
У вас есть Модель и есть к ней разные оболочки: WPF (MVVM), Формы (MVP), Консоль (MVC).
В любом из этих приложений вам нужна "просто выборка".
Как будет правильно сделать: реализовать её три раза в ViewModel, в Presenter и в Controller или один раз в Модели?
как я понимаю - в описанной ране цепочке должен появится дополнительный слой абстракции
View
ViewModel
Model (вся логика тут)
IUnitOfWork(IRepository)
BackEndServer (для логике на сервере туда-сюда придется постоянно гонять кучу данных, что не хорошо)
DataBase
В целом вроде и логично, но:

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Зависит от сложности запроса.
Например, создать сложный SQL или LINQ запрос может VM, но исполнять его же будет Модель.
Чем меньше логики в VM - тем чище будет реализация паттерна.
Каждый запрос, каждая логика, каждые действия с данными требуют получения множества вводимой информации из View, и что бы это работало - ViewModel эти данные должна передать. И если таких данных очень много - то VM превращается в целую простыню кода чисто передающего свойства из View в Model, что прям ощущается как-то неправильно. Создается куча кода по сути с нулевым функционалом. При том любое изменение логики связанное со структурой данных приведет к изменению сразу всех слоев, что тоже неудобно. А уж если учесть что слоев абстракции стало не 5 а 6 - то ещё больнее.

Тогда я начинаю терять смысл - а зачем нужда VM? Весь визуал и обработка данных для него - это View, вся работа с данными Model, что делает ViewModel - просто передает данные между View и Model? Не приведет ли это просто к лишнему коду?
Как правильно организовать ViewModel, что бы при добавлении ещё одного поля в View связанного с некоторым свойством в Model минимизировать число кода для этой связи.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Команда используются по нескольким причинам:
1) Её можно передать через свойство. А привязка к свойствам это один из основных механизмов WPF;
2) В команде есть члены для проверки её состояния. Что в обобщённом виде очень полезно для многих методов.
Это всё понятно, как я понимаю - это игры с терминами, не "обработка события", а "обновление состояния". (по факту же мы всё равно реагируем на событие некоторым обработчиком, что по сути то же самое)

Если считаете что я наглею с вопросами, можете не отвечать , я просто сам пытаюсь понять как надо и пока я не очень понимаю некоторые вещи, точнее возникают мысли вида "а зачем? а точно ли так?".
более конкретный пример

Такой, более конкретно реальный мой пример (насколько смог упрощенный), который я не понимаю как правильно реализовать через V-VM-M, точней мне кажется, что всё получится неудобоваримо сложно.

Есть целая плеяда разнообразных таблиц (справочники, трубы такие, трубы сякие, оборудование, штучки цепляемые на трубы, междутрубные штучки, инструмент цепляемый на трубы..) - в виде ObservableCollection<у каждой таблицы свой> в VM.
Все таблицы имеют отображение в View.

Есть общая длинная труба набираемая/изменяемая из элементов выше (путем преобразования их по некоей сложной и разной логике к объектам данной коллекции с учетом множества параметров). Труба и необходимые параметры имеют отображение в View. Труба привязана к своей ObservableCollection, параметры к своим свойствам.

Есть ещё множество другой хитрой и сложной логики для работы/сортировки/анализа/поиска в указанных таблицах.

На текущий момент все данные сначала целиком получаются с сервера, в процессе работы хранятся в VM в указанных коллекциях и посредством нужных команд преобразовываются необходимым образом с учетом параметров. И в конце нажатием кнопки "Сохранить" отправляются через IRepository(если не вдаваться в подробности) на сервер.

Логика работы с данными сложная, и исходя из описанного вами выше должна быть вынесена из VM в отдельную абстракцию model.
Но параметров и таблиц много, я бы сказал очень-очень много в очень большом числе окон, и получается так, что их все надо передавать, что приведет к множеству кода исключительно для передачи этих данных.

Помимо вышеуказанного для меня уже проблема - это слишком больше число уровней абстракции (перед UnitOfWork у меня там есть ещё слои обработки), а тут ещё один. А разработка идет по пути постоянной переделки всей структуры данных и я не понимаю как мне надо это организовать, что бы минимизировать боль.
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
02.08.2023, 16:22
Цитата Сообщение от werymag Посмотреть сообщение
ибо расчетные сервера крайне дорогая штука
Эмммм... А что это такое?
Цитата Сообщение от werymag Посмотреть сообщение
Такой, более конкретно реальный мой пример
А DTO сложно использовать? Их для этого и придумали, гонять данные между слоями... Модель выдаёт готовые объекты DTO (Коллекции DTO типа List<T>), VM их переваривает, превращает в ObservableCollection<T> и объекты INPC, и выдаёт для View. Потом всё в обратном порядке. Из ObservableCollection<T> получить List<T> для Model можно командой из 8-ми символов... Так же и с объектами. Можно DTO сделать с INPC, тогда останутся только коллекции.

Добавлено через 2 минуты
Вариантов куча, но остаётся всегда одно - вся логика в Model, что бы это ни было (СУБД, WEB, File и т.д.). VM просто отражает данные Model для View. Таков паттерн и ничего с этим не поделать...

Добавлено через 1 минуту
werymag, И гонять между слоями готовые объекты, а не наборы таблиц и потом их где-то склеивать и расклеивать..
0
 Аватар для werymag
27 / 12 / 1
Регистрация: 20.05.2015
Сообщений: 217
02.08.2023, 17:37
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Эмммм... А что это такое?
Расчетные сервера? Ну просто сервера с множеством процессоров/видеокарт огромными объемами оперативной памяти, а, их их ещё называют суперкомпьютеры или минисуперкомпьютеры. На самом деле не такая прям и редкость в расчетных контрах и вузах, просто дорогая и заморочистая в части доступа.

Цитата Сообщение от Andrey-MSK Посмотреть сообщение
А DTO сложно использовать? Их для этого и придумали, гонять данные между слоями... Модель выдаёт готовые объекты DTO (Коллекции DTO типа List<T>), VM их переваривает, превращает в ObservableCollection<T> и объекты INPC, и выдаёт для View. Потом всё в обратном порядке. Из ObservableCollection<T> получить List<T> для Model можно командой из 8-ми символов... Так же и с объектами. Можно DTO сделать с INPC, тогда останутся только коллекции.
Ну оно почти так сейчас и работает (за исключением логики в VM):
- открывая окно создавю головную VM
- в конструкторе VM через специальный класс "подключения" делаю запросы к серверу (IRepository)
- в этом классе получаю DTO объекты с сервера
- преобразую DTO в нужные коллекции объектов и возвращаю эту коллекцию в VM.
- эти объекты/коллекции я уже и гоняю в VM по вышеописанной логике.

Но добавление Model подразумевает, что эти коллекции я долен получить в нем(в Model), и далее постоянно(при работе) передавать их(или изменения в них) в VM(а та в View) и обратно.
И вот это процесс передачи мне пока не понятен. У меня из идей только просто пробрасывать ссылки, либо же писать просто огромные простыни кода где я должен подписываться на изменения Model, передавать туда копии, забирать оттуда копии при событии изменения, ну и т.п. Что увеличить количество кода в полтора раза. (и по мне нарушает принцип Kiss).

Для меня сейчас скорее вопрос даже не в том как точно сделать патерн MVVM, а как вынести логику из VM наиболее правильным способом, потому что сейчас классы VM у меня просто огромные (много свойств, и очень много логики, даже с учетом того, что я их группирую). Потому решил вот почитать примеры да поспрашивать умных людей , пока я не вижу тут решения, вместо огромных VM я получу средние VM и всё такие же огромные Model, то есть в полтора раза больше кода. Но, возможно, я не понимаю как оно работает.

Так же мне не понятно, а class Model (с логикой и данными) должен быть один для программы? Я могу создавать его в конструкторе VM или должен его получать? Или это не регламентируется?
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
02.08.2023, 17:56
Цитата Сообщение от werymag Посмотреть сообщение
а class Model (с логикой и данными) должен быть один для программы?
Один. Обычно запускается сервисом через DI.
Цитата Сообщение от werymag Посмотреть сообщение
Расчетные сервера?
Я имел ввиду просто сервер СУБД, ПО которого рассчитано на выполнение разных по сложности запросов на SQL. Этим сервером может быть любой ПК с многоядерным процессором и достаточным объёмом RAM.

Добавлено через 5 минут
werymag, Потому и говорю что пусть сервер СУБД высчитывает все нужные варианты и возвращает единый набор данных за раз. А не гонять кучу таблиц в Model, там их сортировать, склеивать, расклеивать и т.д.

Добавлено через 6 минут
werymag, И тем же вариантом запись в БД. Отправить на сервер объект, а там уже с помощью хранимки раскидать все нужные данные по таблицам с соблюдением целостности данных. Учите SQL и учитесь пользоваться серверным ПО вашей СУБД, и не выдумывайте проблемы на ровном месте...
0
 Аватар для werymag
27 / 12 / 1
Регистрация: 20.05.2015
Сообщений: 217
02.08.2023, 18:11
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
werymag, Потому и говорю что пусть сервер СУБД высчитывает все нужные варианты и возвращает единый набор данных за раз. А не гонять кучу таблиц в Model, там их сортировать, склеивать, расклеивать и т.д.
Но тут это приведет к постоянному ожидания ответов от сервера, а, с высокой вероятностью, пользователи программы будут сидеть где-нибудь на севере Якутии через спутниковый интернет.

Из ваших слов я делаю вывод, что постоянные обращения к серверу это в порядке нормы? В этой части я особо не разбираюсь, но меня убедили (и я сам удостоверился), что работа через сеть это всегда самое долгое в работе программы, прям усиленно старался делать минимум запросов к серверу: сразу всё нужное получить, сразу всё измененное отправить. Если же делать на каждое нажатие кнопки запрос к серверу то отзывчивость программы станет просто ужасной. Я так понимаю это Web подход.

Цитата Сообщение от Andrey-MSK Посмотреть сообщение
werymag, И тем же вариантом запись в БД. Отправить на сервер объект, а там уже с помощью хранимки раскидать все нужные данные по таблицам с соблюдением целостности данных. Учите SQL и учитесь пользоваться серверным ПО вашей СУБД, и не выдумывайте проблемы на ровном месте...
Как я выше писал, дело то не том как раскидать это по СУБД (с этим нет проблем), дело в логике по которой данные надо обрабатывать и преобразовывать. Большая часть логики связаны не с взять из БД или запихать аБД (я бы сказал это вообще мизерная часть).
О каком серверном ПО моей СУБД речь? Насколько я знаю, оно не умеет рассчитывать изменение диаметра трубы от внутреннего давления, или предельное усилие до разрушения? Или собрать кучу данных, провести над ними кучу сложных действий, а затем опять распихать по разным таблицам? Только если мне писать функции на SQL с математикой и всей этой логикой.
Вот эту часть вообще не понял.

Добавлено через 3 минуты
Это не логика запросов уровня LINQ, там гораздо более сложная логика. Даже примерно не представляю как такое можно реализовать.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
02.08.2023, 19:17  [ТС]
Цитата Сообщение от werymag Посмотреть сообщение
Тогда я начинаю терять смысл - а зачем нужда VM? Весь визуал и обработка данных для него - это View, вся работа с данными Model, что делает ViewModel - просто передает данные между View и Model? Не приведет ли это просто к лишнему коду?
Стоит ли у вас задача абстракции Модели, Представления?
Даже в какой-то отдалённой теории возможно ли изменение типа View? Модели?

В вашей задаче важным аспектом является локальное кеширование данных.
Как бы медленно не работал инет или сеть, запрос с фильтрацией и сортировкой к серверу это всё равно быстрее чем получение полного набора, а потом его локальная обработка.
Совсем другое дело, если имеется локальный кеш и можно проводить обработку без запроса к БД.

В приложении с клиент-серверной архитектуре Модель - это сервер с БД. Клиент это View и VM.
VM отвечает за стыковку View с Сервером. НО!!! В такой стыковке важное значение имеет уже и организация связи через сеть, в том числе создание кеша.
Если рассматривать ТОЛЬКО клиента, то в нём тоже есть часть отвечающая за работу с "настоящими" данными: запросы БД, кеширование, работа с сетью и т.п. С точки зрения приложения клиент-сервер - это можно назвать Локальной Моделью, которая является частью ViewModel. С точки зрения Клиента - это Модель, так как за ней абстрагирована Бизнес Логика.

Пример. EF Контекст БД - он формирует SQL запросы, кеширует данные. Но он же не является частью сервера. Это часть клиента. Тем не менее принято считать, что является частью Модели. А в некоторых задачах он сам является полноценной Моделью. Всё нужное кеширование реализовано в нём.

Так же и вашем случае. Вы верно добавили дополнительный уровень.
Но вот осмысленность его реализации..... Очень часто в WPF применяется совмещённая реализация VM+Model. Если работа над приложением идёт "в одного", если нет в планах никакой модернизации в будущем, то такое решение вполне оправданно по затратам "здесь и сейчас".

ООП в целом - это про коллективное программирование, контроль качества, модернизацию и поддержку приложений. ООП Паттерны приложений и разрабатывались с такой нацеленностью. Почти всегда они требуют большего кода, чем реализация "напрямую без паттернов". И оправдываются дополнительные затраты на них именно только при поставленных выше целях.

Добавлено через 3 минуты
Цитата Сообщение от werymag Посмотреть сообщение
Это всё понятно, как я понимаю - это игры с терминами, не "обработка события", а "обновление состояния".
Неее.
Здесь принципиальная разница. Слушатель знает об источнике события, а источник о слушателе - нет.
Поэтому вызов членов VM из обработчика события в View - это нормально.
А прослушка в VM событий View - это грубое нарушение паттерна.

Добавлено через 50 секунд
Цитата Сообщение от werymag Посмотреть сообщение
Если считаете что я наглею с вопросами,
Ни сколько.
Пишите, интересуйтесь - сколько влезет.

Добавлено через 6 минут
Цитата Сообщение от werymag Посмотреть сообщение
Есть целая плеяда разнообразных таблиц (справочники, трубы такие, трубы сякие, оборудование, штучки цепляемые на трубы, междутрубные штучки, инструмент цепляемый на трубы..) - в виде ObservableCollection<у каждой таблицы свой> в VM.
По сути вы описали Local из DbContext. Эти ObservableCollection являются частью Модели. Зачем вы "руками" делаете то, что уже есть в DbContext... не понятно.
Даже если вы не используете EF, то это всё равно задача слоя работающего с БД (посылающего запросы к БД).

Добавлено через 4 минуты
Цитата Сообщение от werymag Посмотреть сообщение
Есть ещё множество другой хитрой и сложной логики для работы/сортировки/анализа/поиска в указанных таблицах.
По паттерну - это часть Модели. Например, напишите свой IQueryable метод для формирования такого запроса к БД с использованием локального кеша.

Но, ещё раз. Если у вас не стоит задача абстракции View, вы не собираетесь её менять, то можно реализовать VM+Model.
Да, это нарушение паттерна. Но конкретно в вашей реализации, такое нарушении вполне может быть оправдано. Это уже вам оценивать и решать.
1
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
03.08.2023, 08:36
Цитата Сообщение от werymag Посмотреть сообщение
Насколько я знаю, оно не умеет рассчитывать изменение диаметра трубы от внутреннего давления, или предельное усилие до разрушения?
SQL это тот же язык программирования. У него есть все базовые математические функции и рассчитать он может много чего.
Цитата Сообщение от werymag Посмотреть сообщение
Это не логика запросов уровня LINQ
Дак у вас EF? Ну тогда могу посочувствовать...
Цитата Сообщение от werymag Посмотреть сообщение
Даже примерно не представляю как такое можно реализовать.
Написать на SQL хранимки, UDF.
Если не хватает SQL, то, например, MS SQL Server позволяет написать UDF на C#, а там можно такого навертеть...
Цитата Сообщение от werymag Посмотреть сообщение
сразу всё нужное получить, сразу всё измененное отправить.
Вот в этом и ошибка. Сколько по времени займёт получить 1000000 записей из БД и сколько времени уйдёт на получение 10? Даже если вам придётся на сервере СУБД запустить запрос с расчетом чего-то, то это всё равно будет быстрее, чем получить из сети 1000k записей и потом с ними что-то крутить на клиенте. Сервер СУБД работает очень быстро, он рассчитан на выполнение многих сложных запросов параллельно и отрабатывает их довольно таки быстро.
У меня есть хранимка, которая в общей сложности обрабатывает около 1500k записей из 8 таблиц и выдаёт всего 12 записей. Дак работает она около 15мс. Это долго? И это на MS SQL Express с двумя ядрами и 4-мя гигами оперативки...
Цитата Сообщение от werymag Посмотреть сообщение
пользователи программы будут сидеть где-нибудь на севере Якутии через спутниковый интернет.
Тем более вот с такой постановкой задачи... Тянуть кучу записей на клиента такое себе удовольствие...

Либо ещё вариант. Сервер СУБД и WEB находятся на одной машине (быстрой сети). WEB по запросу клиента тянет на себя все данные которые нужны для расчетов, считает их и выдаёт объект с расчетами клиенту. Загрузка сети минимальна. Но опять же... Скорость работы запросов СУБД гораздо быстрее чем то, что вы наваяете в WEB для расчетов. Задумайтесь... Сервера СУБД не зря придумали, это очень мощная и быстрая система управления данными, а не только инструмент хранения данных...

LINQ (EF) очень ограничен в составлении нормальных сложных запросов. Если нужно реально что-то быстрое, сложное и индивидуальное - то только реализация на диалекте SQL вашей СУБД... А EF оставьте для приложений типа Hello World!, на большее он не способен...
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
03.08.2023, 09:58  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Написать на SQL хранимки, UDF.
На EF тоже можно добавлять пользовательские функции.
Вот правда не могу сказать на какой стороне они исполняются: на клиенте или сервере... Чёрт его знает. Я в этой части только самые вершки знаю.
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
03.08.2023, 10:15
Цитата Сообщение от Элд Хасп Посмотреть сообщение
на какой стороне они исполняются
На сервере. EF просто забирает из них набор данных.

Все запросы, созданные EF, исполняются на сервере, но качество этих запросов оставляет желать лучшего...
Лучше вместо него использовать Linq2DB. Он более грамотно подходит к генерации кода SQL. Работает он гораздо быстрее, но это совсем другая технология. Он кардинально отличается от EF.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
03.08.2023, 11:43  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Лучше вместо него использовать Linq2DB. Он более грамотно подходит к генерации кода SQL. Работает он гораздо быстрее, но это совсем другая технология. Он кардинально отличается от EF.
Да.
В Linq2DB по сути только транслятор с LINQ в SQL.

EF гораздо больше "берёт на себя".
Что касается создаваемых им SQL запросов - согласен. Что-то более менее сложное EF "не по зубам". Но вроде в нём не проблема расширить функционал внедрением SQL, созданием своих функций.

В любом случае, если есть какая-то Бизнес Логика кроме работы с БД, то лучше эту работу инкапсулировать в Репозитории внутри Модели. А как выполнен это репозиторий (SQL, EF, Linq2Db или всё вместе) - это уже вторичный вопрос.
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
03.08.2023, 11:48
Элд Хасп, Если можно переложить расчёты на сервер СУБД, то лучше уж пусть он ими занимается, он с этим справится гораздо лучше и быстрее, а в коде просто получить набор данных
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
03.08.2023, 12:44  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Если можно переложить расчёты на сервер СУБД, то лучше уж пусть он ими занимается, он с этим справится гораздо лучше и быстрее, а в коде просто получить набор данных
Чаще всего -да.
Но условия могут быть разные.
Например, используемый набор объектов в локальном объекте небольшой.
В какой-то момент (например при запуске), нужны все объекты.
Связь с сервером "через пол света".

Получаем: данные всё равно уже скачаны локально, новые запросы к серверу выполняются ультра долго. Оптимально тогда работать по локальному кешу.
Другое дело - этот локальный кеш явный и работать с ним нужно иным способом чем с БД. В том числе явно выявлять обновления, синхронизировать и т.п.
Или этот кеш инкапсулирован за "мордой" Репозитория. И для внешнего пользователя безразлично где конкретно находятся данные.
В EF DbContext уже внедрён второй способ. Я не знаю есть ли такое в Linq2Db. Но с явным использованием SQL команд - такая реализация точно будет затруднительной.
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
03.08.2023, 14:11
Элд Хасп, И в многопользовательской среде этот кеш устареет через пару секунд после запуска приложения... Так что толку от него...

Добавлено через 4 минуты
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Связь с сервером "через пол света".
И что тут такого? Если получаешь только нужные данные, а не как ТС 100500100 записей за один запрос, то работает это всё быстро.
У меня ребята работают на Дальнем Востоке и Сахалине с сервером СУБД в Москве. Инет мобильный и всё летает, никто еще не жаловался что медленно грузится. Весь доступ к БД написан на SqlCommand.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
03.08.2023, 14:48  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
И в многопользовательской среде этот кеш устареет через пару секунд после запуска приложения... Так что толку от него...
Там же встроена проверка актуальности.
Если устарели - то подгружаются новые.

Цитата Сообщение от Andrey-MSK Посмотреть сообщение
И что тут такого? Если получаешь только нужные данные, а не как ТС 100500100 записей за один запрос, то работает это всё быстро.
Он же уже отвечал.
Почти все нужные данные уже сразу погружены.
И данных там на мильон-мильонов, а немного. Все помещаются в памяти.

Я прекрасно понимаю, что вы советую.
Но задача и условия у werymag совсем не те о которых вы пишите.
0
 Аватар для Andrey-MSK
3386 / 2272 / 388
Регистрация: 14.08.2018
Сообщений: 7,690
Записей в блоге: 4
03.08.2023, 15:00
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Но задача и условия у werymag совсем не те о которых вы пишите.
Дак может стоит werymag пересмотреть стратегию в более правильную сторону, а не героически преодолевать трудности, которые сам себе создал?
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
03.08.2023, 15:00

WPF конвертеры [Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html View получает данные от ViewModel, но часто бывают случаи...

Элемент геометрии «Стрелка» [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Реализация сделана по вопросу отDissolve в теме...

WPF vs WinForms (для начинающих) [Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Эту тему решил создать, так как очень часто сталкиваюсь с...

WPF команды и MVVM. Часть 2. Всплытие команд. Реализация команды для списка элементов [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html На практике часто встречаются случаи когда команда и кнопка...

Обсуждение темы "Библиотека элементов для реализации WPF MVVM Решений" [WPF, Элд Хасп]
Любое обсуждение, рекомендации, вопросы и т.п. по теме https://www.cyberforum.ru/wpf-silverlight/thread2738784.html В том числе по...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
Как ИИ начал спорить и врать (возможно почуяв опасность для себя от индустрии - уход от электроники).
Hrethgir 04.08.2026
Недельный диалог, на фоне событий с НПЗ. Да, из спирта можно получать бензин, и это не сложно. Но потом в схеме я решил избавиться от насоса, при этом полностью сделав контроль подачи спирта в. . .
Термопринтер QR701
Argus19 03.08.2026
Термопринтер QR701 Купил два термопринтера QR701. На сэлф-тесте написано: Language: PC936 (GB18030). Что означает, что принтеры могут печатать только латиницу и китайские иероглифы. Так же. . .
Создание формы заимствованного документа
Maks 03.08.2026
Задача: Необходимо создать собственную форму заимствованного документа. На форме должен быть реквизит "Покупатель", а также табличная часть со следующими реквизитами: - Расчетный счет покупателя. . .
Задача предоставления скидок покупателям
Maks 03.08.2026
Задача: В документе "Продажи" необходимо реализовать функционал предоставления скидок покупателям. Скидка должна автоматически рассчитываться и подставляться в соответствующее поле при выборе. . .
Почему SEO не начинается с ключевых слов: что проверить до написания текстов
Neotwalker 01.08.2026
Когда владельцу сайта предлагают заняться SEO, первым шагом часто становится сбор запросов и написание текстов. Логика кажется понятной: 1. Находим ключевые слова. 2. Добавляем их на. . .
Знание — сила: Доктрина интенциональности знаний, углубление в формулу
Hrethgir 01.08.2026
https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11957&stc=1&d=1785567302 Знаменитый афоризм Фрэнсиса Бэкона «Знание — сила» (Scientia potentia est) в массовой культуре принято понимать. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru