Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.69/26: Рейтинг темы: голосов - 26, средняя оценка - 4.69
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175

Разделение функционала между слоями MVVM на примере создания простого WPF приложения с БД

10.02.2024, 00:12. Показов 8826. Ответов 182
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Тема создана разделением темы Вывод на печать изображения MVVM


Цитата Сообщение от Элд Хасп Посмотреть сообщение
Вы имеете - некое свойство может иметь значения разных типов (или даже одного типа) и View выводит разные представления для разных значений, то это снимает часть противоречий.
Да про это. например в
XML
1
 <ContentControl Grid.Column="1" Content="{Binding CurrentView}"></ContentControl>
идет привязка к свойству CurrentView, оно же изменяется при переключение RadioButton. То есть от выбранного RadioButton зависит какой контент будет у ContentControl. Получается что представление очень зависимо от свойства CurrentView.

Кликните здесь для просмотра всего текста
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
public class MainWindowVM : ViewModelBase
    {
        public MainWindowVM()
        {
            //Создаём команды
            OpenUserControl1Command = new RelayCommand(OpenUserControl1);
            OpenUserControl2Command = new RelayCommand(OpenUserControl2);
            OpenUserControl3Command = new RelayCommand(OpenUserControl3);
 
            OpenOtherControlCommand = new RelayCommand(OpenOtherControlCommandExecute);
        }
        #region открытие user control
        private object _popupCurrentView;
 
        public object PopupCurrentView
        {
            get => _popupCurrentView;
            set
            {
                _popupCurrentView = value;
                OnPropertyChanged(nameof(PopupCurrentView));
            }
        }
        public ICommand OpenOtherControlCommand { get; private set; }
        public void OpenOtherControlCommandExecute(object obj) => PopupCurrentView = new SelectLangue();
 
        public ICommand CloseOtherControlCommand { get; private set; }
        public void CloseOtherControlCommandExecute(object obj) => PopupCurrentView = null;
        #endregion
 
        private object _currentView;
 
        /// <summary>
        /// ListcurrentView нужен для хранения состояния UserControl. 
        /// Все действия над представлением и изменением контента сохраняются в этой коллекции. 
        /// Если хранить  представление и изменением контента не нужно. Тогда лучше удалить эту коллекцию и связанный с ней метод.
        /// </summary>
        private List<UserControl> ListcurrentView = new();
 
        /// <summary>
        /// Отображание текущего UserContol в ContentControl в окне MainWindow
        /// </summary>
        public object CurrentView
        {
            get => _currentView;
            set
            {
                _currentView = value;
                OnPropertyChanged(nameof(CurrentView));
            }
        }
        #region Команды для отображения UserControl в окне MainWindow
        public ICommand OpenUserControl1Command { get; }
        private void OpenUserControl1(object obj) => CurrentView = GetOrAddUserControl<UserControl1>();
 
        public ICommand OpenUserControl2Command { get; }
        private void OpenUserControl2(object obj) => CurrentView = GetOrAddUserControl<UserControl2>();
 
        public ICommand OpenUserControl3Command { get; }
        private void OpenUserControl3(object obj) => CurrentView = GetOrAddUserControl<UserControl3>();
 
        /// <summary>
        /// Универсальный метод для возвращения UserControl из коллекции
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private UserControl GetOrAddUserControl<T>() where T : UserControl, new()
        {
            var control = ListcurrentView.Find(c => c.GetType() == typeof(T));
            if (control == null)
            {
                control = new T();
                ListcurrentView.Add(control);
            }
            return control;
        }
        #endregion
    }


Цитата Сообщение от Элд Хасп Посмотреть сообщение
И такое переключение должно быть реализовано с помощью навигатора в View.
Использование сервисов, чтобы узнать об изменение View?
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
10.02.2024, 00:12
Ответы с готовыми решениями:

ASP.NET MVC - разделение функционала между различными view
Добрый день)) Хочу спросить совета. В своем проекте использую MVC + jQuery, чтобы создать функционал в системе, аналогичный тому, что...

WPF MVVM: варианты создания VM с параметрами
Есть обычная ViewModel: public class SomeViewModel { private readonly ILogger logger; private readonly...

WPF нюансы создания проводника и мелочи по MVVM
Здравствуйте. Я только начинаю осваивать WPF и паттерн MVVM, и вот в чём загвостка: 1) Я уже написал визуальное оформление приложения и...

182
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
12.02.2024, 18:29
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от xellan24rus Посмотреть сообщение
Кстати какую версию ef вы используете?
Я не менял.
Версия какая стояла у вас такая и стоит.

Добавлено через 16 секунд
Цитата Сообщение от xellan24rus Посмотреть сообщение
В бд данные есть.
А вопрос в чём?
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
12.02.2024, 19:27  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А вопрос в чём?
Я скрин прикреплял, у меня возвращает 0 метод
C#
1
Context.People.Local.ToObservableCollection()
Добавлено через 3 минуты
Элд Хасп, разобрался.
C#
1
2
  Context.People.Load();
  Context.Product.Load();
Без этого не работает

Добавлено через 29 минут
Элд Хасп, я сейчас все прелести mvvm почувствовал. Сильно изменил проект. На view это даже никак не отразилось, ни едной ссылки в ней не поменял

Добавлено через 20 минут
Элд Хасп, обновил репозиторий. Не много сократил ваш код, вынес класс авторизации в отдельный. Но ваш метод авторизации не смог сделать пока что.
Если по функционалу у нас есть отличается, то доделаю его в проекте. Сейчас отличи с гит я не вижу, но и иза слияния не знаю, есть или нет они. А целом вы можете заменить проект в своей ветке на мой, чтобы не было сильных отличий для будущей работы.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
13.02.2024, 01:31
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Закину в репозиторий реализацию.
xellan24rus, закинул.
Убрал лишнее оттуда, но не проверял на ошибки.
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
13.02.2024, 14:01  [ТС]
Элд Хасп, я перенесу в свою ветку нужную часть вашего обновления. А в целом проекты до сих сильно отличаются, я изменил код репозитория на другой, основные команды Add, Remove Они удобны для использования из другого класса, но функционал по типу FirstOrDefault, Any, Where, Include, в обычном проекте без этого никак, из вариантов делать методы с перезагрузкой разной, для разных типов, но этот код не универсален. Поэтому реализовал обобщенные методы этих команд. В целом это работает, но использование не очень удобное ибо метод нужно приводить к нужному типу при вызове.
Пример
C#
1
  public bool Any<T>(Func<T, bool> predicate) where T : class =>  Context.Set<T>().Any(predicate);
Вызов
C#
1
 if(user.Any<User>(x => x.Name == _user.Name));
Конечно это не сильно достает, но если сравнивать с тем что можно объявить Context и напрямую пользоваться linq
C#
1
user.Any(x => x.Name == _user.Name)
То код для этих команд немного усложняет всё и так как в репозитории нет никакой логики кроме реализации базовых команд, то он становится не нужным, так как с контекстом бд напрямую работать быстрее, так как не нужно указывать обобщенные типы каждый раз.
Но для примера разделения логики это хороший вариант.

Добавлено через 2 часа 56 минут
Цитата Сообщение от xellan24rus Посмотреть сообщение
в обычном проекте без этого никак, из вариантов делать методы с перезагрузкой разной, для разных типов, но этот код не универсален
Покопавшись на гит в репозитории ef нашел ответы об универсальном методе Set, для ещё большей универсальности нужно пользоваться рeфлексией, так как штатными средствами DbSet не указать, не используя универсальный метод с ограничением .
Вышло о чем писал выше.
C#
1
2
3
4
5
6
7
Command<People> people = new Command<People>();
var ss = people.Any(x => x.CompletedTasks == 1);
var re = people.Where(x => x.Name == x.Name).ToList();
 
Command<Product> product = new Command<Product>();
var res = product.Where(x => x.Name == x.Name).ToList();
var ssss = product.Any(x => x.Description == "");
Функций больше в проекте, основные реализовал. На счёт Set однако много тем создано, так как использование его убирает нужду писать с каким DbSet идёт работа.
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 18:14  [ТС]
Элд Хасп, освободился немного наконец то. Обновил решение на гит, добавил настройки приложения, вывод и работу с товарами, добавил вашу реализацию с проекта Common(решение впечатляет) для бд сделал Crud.
Авторизацию по вашему примеру не сделал, не пойму как связать события Vm и т.п. Вашу ветку можете заменить на мою, так будет лучше, приведем ветки к единому решению наконец то)

Добавлено через 1 час 50 минут
Элд Хасп, сделал авторизацию используя события. Теперь жду критику\советы)
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 20:20
Цитата Сообщение от xellan24rus Посмотреть сообщение
сделал авторизацию используя события. Теперь жду критику
C#
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
        public void Authorize(string? name)
        {
            IsAuthorizedChangedEventArgs args = new IsAuthorizedChangedEventArgs(!string.IsNullOrEmpty(name));
            if (args.IsAuthorized)
            {
                User? old = Context.User.FirstOrDefault(x => x.Name == name);
                if (old is null)
                {
                    Context.User.Add(new User() { Name = name, IsAuthorized = true });
                    Context.SaveChanges();
                }
                else
                {
                    old.Name = name!; // По условию поиска Name == name. Зачем его перезаписывать?
                    Context.User.Update(old);
                    Context.SaveChanges();
                }
            }
            else
            {
                Context.SaveChanges(); // Зачем сохранение, если не было изменений?
            }
 
            IsAuthorized = args.IsAuthorized;
            AuthorizedChanged?.Invoke(this, args);
        }
    }
Добавлено через 5 минут
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
        public void Authorize1(string? name)
        {
            bool isAuthorized = false;
            if (!string.IsNullOrEmpty(name))
            {
                isAuthorized = true;
                User? old = Context.User.FirstOrDefault(x => x.Name == name);
                if (old is null)
                {
                    Context.User.Add(new User() { Name = name, IsAuthorized = true });
                    Context.SaveChanges();
                }
                else if(!old.IsAuthorized)
                {
                    old.IsAuthorized = true;
                    Context.User.Update(old);
                    Context.SaveChanges();
                }
            }
 
            IsAuthorizedChangedEventArgs args = new IsAuthorizedChangedEventArgs(isAuthorized);
            IsAuthorized = args.IsAuthorized;
            AuthorizedChanged?.Invoke(this, args);
        }
1
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 20:21  [ТС]
Элд Хасп, тут точно нужно подправить, похоже остатки старого кода это
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 21:22
C#
120
        public void CopyProperties(object source, object destination)
Это статический метод и нужно вынести его в отдельный класс общего использования:
C#
1
2
3
public static class Mapper
{
        public void CopyProperties(object source, object destination)
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 21:24  [ТС]
Элд Хасп, принял, перемещу.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 22:01
Вот мой класс для работы с PropertyDescriptor
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
 
namespace CommonCore.Helpers
{
    public static partial class PropertyDescriptorHelper
    {
        private static readonly Dictionary<Type, PropertyDescriptorCollection> types = new();
 
        private static PropertyDescriptorCollection GetProperties(Type targetType)
        {
            if (!types.TryGetValue(targetType, out var properties))
            {
                properties = TypeDescriptor.GetProperties(targetType);
                types.Add(targetType, properties);
            }
            return properties;
        }
 
        public static void SetValue<T>(this T target, string propertyName, object value)
            where T : class
        {
            GetProperties(target.GetType())[propertyName].SetValue(target, value);
        }
        public static void SetValue<T>(this T target, PropertyDescriptor propertyDescriptor, object value)
            where T : class
        {
            propertyDescriptor.SetValue(target, value);
        }
 
        public static object GetValue<T>(this T target, string propertyName)
            where T : class
        {
            return GetProperties(target.GetType())[propertyName].GetValue(target);
        }
        public static object GetValue<T>(this T target, PropertyDescriptor propertyDescriptor)
            where T : class
        {
            return propertyDescriptor.GetValue(target);
        }
    }
    public static partial class PropertyDescriptorHelper
    {
        /// <summary>Присоединение прослушки изменения свойства.</summary>
        /// <typeparam name="T">Тип прослушиваемоего объекта. <see cref="DependencyObject"/> или производный от него.</typeparam>
        /// <param name="source">Прослушиваемый объект.</param>
        /// <param name="dependencyProperty">Прослушиваемое свойство.</param>
        /// <param name="listener">Метод прослушки.</param>
        public static void AddPropertyChanged<T>(this T source, DependencyProperty dependencyProperty, DependencyPropertyChangedHandler listener)
            where T : DependencyObject
        {
            var descriptor = DependencyPropertyDescriptor.FromProperty(dependencyProperty, source.GetType());
            var propertyHandler = AddPropertyHandler(source, listener, dependencyProperty);
            descriptor.AddValueChanged(source, propertyHandler.Raise);
        }
 
        /// <summary>Отсоединение прослушки изменения свойства.</summary>
        /// <typeparam name="T">Тип прослушиваемоего объекта. <see cref="DependencyObject"/> или производный от него.</typeparam>
        /// <param name="source">Прослушиваемый объект.</param>
        /// <param name="dependencyProperty">Прослушиваемое свойство.</param>
        /// <param name="listener">Метод прослушки.</param>
        public static void RemovePropertyChanged<T>(this T source, DependencyProperty dependencyProperty, DependencyPropertyChangedHandler listener)
            where T : DependencyObject
        {
            var descriptor = DependencyPropertyDescriptor.FromProperty(dependencyProperty, source.GetType());
            var propertyHandler = RemovePropertyHandler(source, listener, dependencyProperty);
            if (propertyHandler != null)
                descriptor.RemoveValueChanged(source, propertyHandler.Raise);
        }
 
        private static readonly ConditionalWeakTable<DependencyObject, Dictionary<(DependencyPropertyChangedHandler handler, DependencyProperty property), PropertyHandler>> handlers = new();
 
        //private static readonly Dictionary<(DependencyObject source, DependencyPropertyChangedHandler handler, DependencyProperty property), PropertyHandler> handlers = new();
 
        private static PropertyHandler AddPropertyHandler(DependencyObject source, DependencyPropertyChangedHandler handler, DependencyProperty property)
        {
 
            if (!handlers.TryGetValue(source, out var properties))
            {
                properties = new();
                handlers.Add(source, properties);
            }
 
            if (properties.TryGetValue((handler, property), out var pHandler))
            {
                pHandler.Count++;
            }
            else
            {
                pHandler = new(handler, property, source.GetValue(property));
                properties.Add((handler, property), pHandler);
            }
            return pHandler;
        }
 
        private static PropertyHandler RemovePropertyHandler(DependencyObject source, DependencyPropertyChangedHandler handler, DependencyProperty property)
        {
            PropertyHandler pHandler = null;
            if (handlers.TryGetValue(source, out var properties))
            {
                if (properties.TryGetValue((handler, property), out pHandler))
                {
                    pHandler.Count--;
                    if (pHandler.Count == 0)
                    {
                        properties.Remove((handler, property));
                    }
                    if (properties.Count == 0)
                    {
                        handlers.Remove(source);
                    }
                }
            }
 
            return pHandler;
        }
 
 
        private class PropertyHandler
        {
            public DependencyPropertyChangedHandler Handler { get; }
 
            public int Count { get; set; }
 
            public DependencyProperty Property { get; }
 
            public object OldValue { get; private set; }
 
            public void Raise(object sender, EventArgs e)
            {
                if (sender is not DependencyObject dObj)
                {
                    throw new InvalidCastException("sender может быть только DependencyObject.");
                }
 
                object oldValue = OldValue;
                object newValue = dObj.GetValue(Property);
 
                Handler(dObj, new DependencyPropertyChangedEventArgs(Property, oldValue, newValue));
 
                OldValue = newValue;
            }
 
            public PropertyHandler(DependencyPropertyChangedHandler handler, DependencyProperty property, object oldValue)
            {
                Handler = handler;
                Property = property;
                Count = 1;
                OldValue = oldValue;
            }
        }
    }
    /// <summary>Делегат метода прослушки изменения <see cref="DependencyProperty"/>.</summary>
    /// <param name="sender">Объект в котором изменилось свойство.</param>
    /// <param name="args">Аргументы изменения.</param>
    public delegate void DependencyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args);
}
Добавлено через 15 минут
Очень надумано класс выглядит:
C#
1
2
3
4
5
6
7
8
9
10
11
    public class Command<T> : IRepository<T> where T : class
    {
        public Context Context; //*Сделал публичным так как реализованы не все функции ef, сейчас они не нужны для реализации. Но можно сделать позже.
                                //Потом сделаю это свойство приватным*//
        public Command()
        {
            Context = new Context();
            Context.Database.EnsureCreated();
            Context.People.Load();
            Context.Product.Load();
        }
Добавлено через 8 минут
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
namespace Repositories
{
    public class PeopleRepository : IRepository<Person>
    {
        private readonly Context context;
 
        public PeopleRepository(Context context)
        {
            this.context = context;
            this.context.Database.EnsureCreated();
            this.context.People.Load();
        }
 
 
    }
    public class ProductsRepository : IRepository<Product>
    {
        private readonly Context context;
 
        public ProductsRepository(Context context)
        {
            this.context = context;
            this.context.Database.EnsureCreated();
            this.context.Products.Load();
        }
    }
C#
1
2
3
4
5
6
7
8
namespace Repositories
{
    public class Authorized
    {
        public Authorized(Context context) =>  this.context = context;
        private readonly Context context;
 
        public bool IsAuthorized { get; private set; }
Добавлено через 1 минуту
Для класса Authorized тоже нужен интерфейс:
C#
1
2
3
4
5
6
7
8
9
10
11
namespace ************
{
    public interface IAuthorized
    {
        bool IsAuthorized { get; }
 
        event EventHandler<IsAuthorizedChangedEventArgs>? AuthorizedChanged;
 
        void Authorize(string? name);
    }
}
Добавлено через 8 минут
Классы Person и Product объявлены в Репозитории.
А репозиторий у нас жёстко завязан на определённое Хранилище.
С другой стороны эти классы проникают до VM, а VM используется в View.

И получается, если мы хотим изменить хранилище, то нам придётся прекомпилировать ВСЕ слои.
А смысл паттернов MV* - обеспечить независимость слоёв.
Следовательно объявление Person и Product в Репозитории полностью ломает концепцию паттернов MV* и Репозитория.

Решение.
1) Сделать Person и Product общедоступными DTO в библиотеке Common;
2) Создать в Common интерфейсы для этих классов и передавать между слоями через интерфейсы.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 22:04
Связи проекта VM:
Название: Снимок экрана 2024-02-26 220155.jpg
Просмотров: 28

Размер: 15.1 Кб

А мы в начале, насколько помню, оговаривали такую схему:
Структура связей в WPF Решении

Следовательно ссылки в проектах решения могут быть только на проект Common.
Только проект App (не View) может ссылаться на другие проекты, кроме Common.
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 22:53  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
класс для работы с PropertyDescriptor
Не пойму как использовать. Это не работает.
C#
1
2
3
4
5
    public ObservableCollection<ProductData> ProductDataList
    {
        get => (ObservableCollection<ProductData>)PropertyDescriptorHelper.GetValue(ProductDataList, "ProductDataList");
        set => PropertyDescriptorHelper.SetValue(ProductDataList, "ProductDataList", value);
    }
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Очень надумано класс выглядит:
Обобщенный, команды доступны для любого класса, дублирование кода становится не нужным.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Для класса Authorized тоже нужен интерфейс:
Добавлю.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А репозиторий у нас жёстко завязан на определённое Хранилище.
С другой стороны эти классы проникают до VM, а VM используется в View.
И получается, если мы хотим изменить хранилище, то нам придётся прекомпилировать ВСЕ слои.
А смысл паттернов MV* - обеспечить независимость слоёв.
Следовательно объявление Person и Product в Репозитории полностью ломает концепцию паттернов MV* и Репозитория.
Vm использует только команды из класса Command, если мы захотим поменять источник данных, то достаточно будет переписать класс Command и источник данных поменяется во всех классах которые используют Command.
Поэтому я удалил реализации классов Person и Product, их код дублируется и если менять источник данных, то придется лезть в оба(все классы)
Если Person и Product завязаны на реализациях своих классов, то почему нельзя использовать один класс который реализует для многих тот же код?
Я привык писать как можно короче, и не хотелось бы дублировать код.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
И получается, если мы хотим изменить хранилище, то нам придётся прекомпилировать ВСЕ слои.
Достаточно только залезть в класс Command, он реализует базовые функции, используемые при работе с данными.
Если нужны были бы функции касательно только одного класса, то без сомнений лучше создать отдельный класс.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
А мы в начале, насколько помню, оговаривали такую схему:
Структура связей в WPF Решении
Я потом писал что решил разделить проект на как можно больше слоев. Конечно можно их объединить в одном решении Common, но это может показаться зависимо немного. И поэтому я разделил всё на как можно больше слоёв, так сказать чтобы показать не зависимость данных.

Добавлено через 14 минут
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Очень надумано класс выглядит:
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Context.People.Load();
            Context.Product.Load();
Как вариант можно сделать отдельный класс для заполнения данных, но этот класс будет ссылаться на базовую реализацию с данными, класс Command, так как за источник данных отвечает класс Command, он сделан под работу с любыми данными, подход CRUD
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3460 / 2781 / 575
Регистрация: 04.09.2018
Сообщений: 8,743
Записей в блоге: 3
26.02.2024, 22:58
Цитата Сообщение от xellan24rus Посмотреть сообщение
Конечно можно их объединить в одном решении Common, но это может показаться зависимо немного
Вот именно - что показаться. Проекты в одном Решении абсолютно не связаны между собой, если специально не пробросить ссылки. Все что их объединяет - это папка на диске, где они лежат. А вот скакать по разным решениям для правки этих проектов - точно будет не удобно.
Цитата Сообщение от xellan24rus Посмотреть сообщение
поэтому я разделил всё на как можно больше слоёв, так сказать чтобы показать не зависимость данных.
Тут надо без фанатизма. Если на отдельный проект будет куча ссылок (зависимостей) в других проектах, то все это "разделение" теряет практический смысл.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 23:12
Цитата Сообщение от xellan24rus Посмотреть сообщение
Обобщенный, команды доступны для любого класса, дублирование кода становится не нужным.
В том и дело, что он не обобщённый.
Что будет если создать такой экземпляр:
C#
1
 private Command<string> CommandString { get; set; } = new();
Цитата Сообщение от xellan24rus Посмотреть сообщение
Не пойму как использовать. Это не работает.
PropertyDescriptor, в отличии от PropertyInfo использует общий с привязками (Binding) механизм уведомления об изменении значения свойства.
Поэтому с помощью PropertyDescriptor можно менять свойства у любого класса, даже без INPC, и привязки автообновят целевой свойство.
См.:
Разъяснение PropertyDescriptor: Почему в некоторых случаях Binding правильно работает без INPC (INotifyPropertyChanged)?
Прослушивание изменений DependencyProperty

Цитата Сообщение от xellan24rus Посмотреть сообщение
Vm использует только команды из класса Command, если мы захотим поменять источник данных, то достаточно будет переписать класс Command
Нет.
Попробуйте каждый проект раскидать по разным Nuget пакетам. Потом измените пакет DataBase и увидите, что для подхвата изменений, придётся перкомпилировать и перезалить все остальные пакеты тоже.

Цитата Сообщение от xellan24rus Посмотреть сообщение
Конечно можно их объединить в одном решении Common
В Common должны находится только платформонезависимые типы общего применения.
Скажем так - это ваш некий кастомный аналог общий библиотек Standard 2.0.

Поэтому простые DTO (лучше иммутабельные классы, записи или, если мутабельные, то структуры) можно помещать в эту библу.

Цитата Сообщение от xellan24rus Посмотреть сообщение
так как за источник данных отвечает класс Command, он сделан под работу с любыми данными,
Так в том, то дело что он не может работать с любыми данными.
Он может работать только с Person и Product. Никакой другой тип этот класс не может принять.
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 23:15  [ТС]
wizard41, судя схеме у меня не хватает только проекта который объединит всё.
Цитата Сообщение от wizard41 Посмотреть сообщение
А вот скакать по разным решениям для правки этих проектов - точно будет не удобно.
View ссылается на проект с Vm, команды Vm ссылаются на один класс.
Но если смотреть зависимости проекта Vm, то DataBase(бд) - если это отдельный проект, то он будет зависимостью везде где идет работа с данными, Repositories содержит команды и интерфейсы, так как команды это работа с данными то DataBase нельзя убрать, Common реализация для работы с Vm(хелпер).
Получается что эти три зависимости обязательны, но можно было сделать в одном проекте их реализацию.
Или же объединить в другом, тогда
Цитата Сообщение от wizard41 Посмотреть сообщение
А вот скакать по разным решениям
Придется скакать ещё больше.

Но так как задача примера разделить всё по слоям, чтобы меньше скакать можно где то сделать зависимости, или же ну или же придётся класс с работой с данными объединить с бд, а отдельную реализацию Элд Хасп удалить из проекта и скопировать её в Vm проект.

Поэтому тут нет фанатизма, в случае с объедением другой проект, он примет все эти зависимости на себя.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 23:18
Цитата Сообщение от wizard41 Посмотреть сообщение
Тут надо без фанатизма.
В данном случае именно ради "фанатизма" эта тема и поднята.
Нужно сделать фанатично правильную реализацию MVVM для учебных целей. Чтобы понять на практике границы функциональных слоёв.

Добавлено через 3 минуты
xellan24rus, так как у вас явно нет понимания, что такое проекты, почему какие-то связи между ними допустимы, а другие нет, то предлагаю ещё усложнить задачу.
Каждый проект делаете в отдельном Решении (но всё в одном Репозитории на GitHub). Каждое решение собираете в пакет и заливаете на NuGet. Все связи между пакетами через NuGet.
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
26.02.2024, 23:45  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
private Command<string> CommandString { get; set; } = new();
Тут только специально сломать можно. таже ассоциация с object, туда хоть что подсунуть можно, без приведения типов, не зная типа это проблема, да и лишняя-не нужная работа. В том же msdn много где object, из за своих только ошибок можно поломать.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
См.:
Суть понял, работа с Dp свойствами. Но тогда наследовать DepencyObject придётся, по другому использовать ваш класс, вроде как не получится.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Он может работать только с Person и Product. Никакой другой тип этот класс не может принять.
Реализация с базовыми функциями работы с данными, методы Add, Remove. Легко переписать на хранение в тот же json, xml. И для всего решения работа с классами останется прежней, только источник изменится. Не пойму зачем для каждого класса дублировать источник, дублировать код.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
В Common должны находится только платформонезависимые типы общего применения.
Скажем так - это ваш некий кастомный аналог общий библиотек Standard 2.0.
Поэтому простые DTO (лучше иммутабельные классы, записи или, если мутабельные, то структуры) можно помещать в эту библу.
Получается DataBase, Repository туда полностью перенести?

Добавлено через 9 минут
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Каждое решение собираете в пакет и заливаете на NuGet. Все связи между пакетами через NuGet.
Так как они имеют зависимости если таковые есть, то получится тоже самое. Сейчас вместо пакетов библиотеки.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
так как у вас явно нет понимания, что такое проекты, почему какие-то связи между ними допустимы
Я понял что надо в Common перенести Repositories который реализует у нас команды и интерфейсы. Касательно DataBase в Vm проекте его можно просто удалить - так как DataBase не является зависимостью в VM. А это остатки кода.
Получается только одна зависимость не в том месте.

Добавлено через 9 минут
Элд Хасп, а с другой стороны если я хотел бы использовать Repositories на сервере чтобы объявить интерфейсы и использовать готовые команда для работы с данными. Если этот проект будет в Common, то зачем мне на сервере реализации INPC из проекта Common.
В случае с ConsoleApp он у меня ссылается только на Repositories, будь серверный проект то достаточно было бы той же зависимости Repositories.
Нагружать решение лишним тоже не очень хорошо, в текущем примере для ConsoleApp не нужно знать что есть реализации INPC если бы я перенес Repositories в Common. Только лишний вес для приложения таким темпом прибавится.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 23:54
Цитата Сообщение от xellan24rus Посмотреть сообщение
Суть понял, работа с Dp свойствами.
Не поняли.

Обычный класс - ваш Person. Без INPC.
К его свойствам привязки в XAML.
Теперь вам нужно поменять значение одного из свойств Person: SelectedPerson.CompletedTasks = 123; Изменив так в Шарпе, вы не сможете изменить представление этого значения в View, так как нет реализации INPC.
Но если измените SelectedPerson.SetValue(nameof(Person.CompletedTasks), 123);, то привязка автообновит представление.

Добавлено через 2 минуты
Цитата Сообщение от xellan24rus Посмотреть сообщение
Получается DataBase, Repository туда полностью перенести?
НЕЕЕЕЕТ!!!

Только интерфейсы и типы используемыми этими интерфейсами.
Реализации интерфейсов, как были в отдельных сборках, так и должны в них оставаться.

Добавлено через 2 минуты
Цитата Сообщение от xellan24rus Посмотреть сообщение
Так как они имеют зависимости если таковые есть, то получится тоже самое. Сейчас вместо пакетов библиотеки.
Не получится.
Вы сейчас не замечаете, что из-за паразитных зависимостей, при изменении DataBase приходится перекомпилировать все вышестоящие проекты. Это происходит фоном и вы, скорее всего, счтитаете даже что этого и не происходит вовсе.

А когда у вас каждая сборка будет в NuGet, когда каждую версию нужно будет "ручками" заливать в NuGet, вы "ой как" прочувствуете что такое паразитные зависимости и почему их следует избегать.

Добавлено через 1 минуту
Даже не в NuGet - можете своё локальное хранилище пакетов сделать.

Добавлено через 55 секунд
Но с NuGet - это тоже очень полезный опыт для разработчика. Поэтому всё же советую освоить как заливать туда пакеты и потом их оттуда использовать.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,169
Записей в блоге: 2
26.02.2024, 23:57
Но с NuGet - это тоже очень полезный опыт для разработчика. Поэтому всё же советую освоить как заливать туда пакеты и потом их оттуда использовать.
Цитата Сообщение от xellan24rus Посмотреть сообщение
так как DataBase не является зависимостью в VM
Является.
Вот здесь зависимость:
C#
36
37
            foreach (var res in CommandProduct.GetProductCollection())
                ProductDataList.Add(new ProductData() { Id = res.Id, Name = res.Name, Description = res.Description });
Раскройте var и увидите - что это DataBase.Product:
0
 Аватар для xellan24rus
364 / 296 / 55
Регистрация: 08.04.2020
Сообщений: 1,175
27.02.2024, 00:02  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
то привязка автообновит представление.
Такое делал, но привык в Vm INPC использовать. Но похоже вашу реализацию не получится использовать без наследования DepencyObject.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А когда у вас каждая сборка будет в NuGet, когда каждую версию нужно будет "ручками" заливать в NuGet, вы "ой как" прочувствуете что такое паразитные зависимости и почему их следует избегать.
Но даже в пакетах везде где используется DataBase, если я изменю к примеру User на Users, то в любом случае мне придется поменять всё... В любом проекте где только есть ссылка на DataBase будь он общим или отдельным, это каснется каждого проектa. И тут не будет разницы где что то я использовал или нет, так как это не избежно.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Но с NuGet - это тоже очень полезный опыт для разработчика. Поэтому всё же советую освоить как заливать туда пакеты и потом их оттуда использовать.
Удобная штука, как другой проект с дизайном закончу, нужно будет попробовать)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
27.02.2024, 00:02

Структура WPF приложения на MVVM
Здравствуйте. Разрабатываю приложение на WPF. Использую шаблон MVVM. В представлении контент сгруппирован по вкладкам TabControl. ...

Пример реализации WPF+MVVM приложения
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Пример решения ТЗ по теме...

Паттерн MVVM или как писать приложения на WPF
Собтвенно вопрос в заголовке. По-скольку WPF поддерживает привязку различным образом наверно это нужно как-то использовать, а не просто как...

Kivy для создания простого приложения
Всем привет ,я делаю простую программу на python, но никак не могу решить 2 проблемы. 1) Никак не меняется фон ModalView, хотя уже все...

Пример переключения между окнами WPF MVVM
Здравствуйте. Изучаю как можно переходить между окнами в wpf в рамках MVVM. Изучаю данный пример: ...


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

Или воспользуйтесь поиском по форуму:
80
Ответ Создать тему
Новые блоги и статьи
Кредитный калькулятор
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