Если нет моделей домена, используется ли ViewModel для модели, чтобы DAL не входил в ViewModel представления? - PullRequest
0 голосов
/ 26 апреля 2011

В настоящее время я работаю над приложением, в котором все его сущности взяты из Linq2SQL. Пытаясь найти способ держать все в чистоте, я добавил модель домена для типа, который пришел из Linq2SQL, и обернул его с помощью ViewModel.

Это немного усложнило мой код, так как у меня есть сервисный уровень, и при инициализации я получил сущность из L2S, обновил свой класс домена и заполнил ее любыми данными, содержащимися в сущности.

Когда я захотел вставить элемент обратно в базу данных в L2S, я столкнулся с проблемой, которая заставила меня сделать наоборот: заполнить сущность данными из класса домена. В этот момент я начал сомневаться, был ли я на правильном пути, поэтому я начал думать о том, что я сделал неправильно, или просто подумал, что это был правильный путь, но в конечном итоге это не так.

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

Итак, отсюда и мой вопрос:

Если моделей домена нет, используется ли ViewModel для модели, чтобы DAL не входил в ViewModel представления?

1 Ответ

1 голос
/ 26 апреля 2011

Ваш вопрос немного расплывчатый. Однако я вижу много путаницы в сообществе по поводу шаблона MVVM. Многие люди «оборачивают» модель во ViewModel, как то, что вы делаете, и это неправильно.

Модель - это в основном ваши объекты L2S.

ViewModel, непосредственно предоставляет объекты L2S и обрабатывает логику взаимодействия, то есть обрабатывает команды, обновления объектов и т. Д. (Ничего не переносит, просто как ссылка на них).

Свертывание неверно, скажем, например, что вы хотите что-то слишком сложное для конвертера, но вам нужно, чтобы оно было в вашей сущности (BusinessObject или что-либо из L2S - вашей ORM-среды), вам следует расширить Организация, поддерживающая это.

Я приведу несколько примеров, однако это несколько иная архитектура:

  • Entity Framework 4.1
  • Prism, реализация MVVM
  • MEF контейнер для инъекций зависимостей

Это список задач в этом приложении, конечный результат - проект в виде представления с диаграммой Ганта.

ViewModel выглядит так:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using Sigep.Common.Interfaces;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.ViewModel;
using System.Windows.Input;
using System.Diagnostics;
using Sigep.Common.DataSelfTracking;
using System.Windows.Data;
using System.Globalization;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Threading;
using System.Threading.Tasks;

namespace Sigep.WPF.Controls.Cronograma
{
    [Export(typeof(TarefasListViewModel))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class TarefasListViewModel : NotificationObject
    {
        private IDataManager _data;
        private IRegionManager _regionManager;

        private static ObservableCollection<Tarefa> _tarefas;
        private static List<Sigep.Common.DataSelfTracking.Tarefa> _tarefasList;

        [ImportingConstructor]
        public TarefasListViewModel(IRegionManager regionManager, IDataManager data)
        {
            _data = data;
            _regionManager = regionManager;

            _tarefas = new ObservableCollection<Tarefa>(_data.TarefaList.OrderBy(T => T.Codigo));
            _tarefasList = _data.TarefaList;

            _data.Loaded += new EventHandler<DataManagerEventArgs>(Data_Loaded);

            _NovaTarefa_Command = new DelegateCommand(this.NovaTarefa);
        }

        void Data_Loaded(object sender, DataManagerEventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                _tarefas.Clear();
                foreach (var tarefa in _data.TarefaList.OrderBy(T => T.Codigo)) _tarefas.Add(tarefa);
                RaisePropertyChanged(() => this.Promotores);
            }
            , CancellationToken.None
            , TaskCreationOptions.None
            , Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<TaskScheduler>());
        }

        public ObservableCollection<Tarefa> Tarefas
        {
            get
            {
                return _tarefas;
            }
        }

        public static void refreshTarefas()
        {
            _tarefas.Clear();
            foreach (var tarefa in _tarefasList.OrderBy(T => T.Codigo)) _tarefas.Add(tarefa);
        }


        public IEnumerable<PromotorIndex> Promotores
        {
            get
            {
                int index = 0;
                foreach (var promotor in _data.Candidatura.Promotores.OrderBy(p => p.Nome))
                {
                    yield return new PromotorIndex()
                    {
                        Nome = promotor.Nome,
                        Index = index++
                    };
                }
            }
        }
        private ICommand _NovaTarefa_Command;
        public ICommand NovaTarefa_Command { get { return this._NovaTarefa_Command; } }
        private void NovaTarefa()
        {
            Tarefa tarefa = new Tarefa { Inicio = DateTime.Now, Fim = DateTime.Now, Nome = "",Actividade = _data.ActividadeList.FirstOrDefault() };

            IRegionManager _region = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IRegionManager>();

            foreach (var v in _region.Regions["CrudCronogramaTarefas"].Views) _region.Regions["CrudCronogramaTarefas"].Remove(v);

            _region.Regions["CrudCronogramaTarefas"].RequestNavigate("/TarefaDefault", nr => { });
            var view = ((FrameworkElement)_region.Regions["CrudCronogramaTarefas"].ActiveViews.FirstOrDefault());

            ((UserControlCrudBase)view).Permissions = Sigep.WPF.Controls.UserControlCrudBase.PermissionsType.Update;

            view.DataContext = tarefa;
        }
    }

    public class PromotorIndex
    {
        public string Nome { get; set; }
        public int Index { get; set; }
    }

    // Converters
    public class DataInicioPercentagemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var tarefa = (Tarefa)value;
            var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

            double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
            double diasTarefaInicio = tarefa.Inicio.Subtract(candidatura.DataInicio).Days;

            return new GridLength((diasTarefaInicio / totalDias * 100), GridUnitType.Star);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

    public class DataMeioPercentagemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var tarefa = (Tarefa)value;
            var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

            double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
            double diasTarefa = tarefa.Fim.Subtract(tarefa.Inicio).Days;

            return new GridLength((diasTarefa / totalDias * 100), GridUnitType.Star);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

    public class DataFimPercentagemConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var tarefa = (Tarefa)value;
            var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

            double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
            double diasTarefaFim = candidatura.DataFim.Subtract(tarefa.Fim).Days;

            return new GridLength((diasTarefaFim / totalDias * 100), GridUnitType.Star);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

    [ValueConversion(typeof(int), typeof(Brush))]
    public class IndexColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType != typeof(Brush))
                throw new InvalidOperationException("The target must be a Brush");

            switch((int)value)
            {
                case 0:
                    return Brushes.Red;
                case 1:
                    return Brushes.Green;
                case 2:
                    return Brushes.Blue;
                case 3:
                    return Brushes.Purple;
                case 4:
                    return Brushes.Yellow;
                case 5:
                    return Brushes.Brown;
                default:
                    return Brushes.Pink;
            }
       }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType != typeof(int))
                throw new InvalidOperationException("The target must be a int");

            var color = (Brush)value;

            if (color == Brushes.Red) return 0;
            else if (color == Brushes.Green) return 1;
            else if (color == Brushes.Blue) return 2;
            else if (color == Brushes.Purple) return 3;
            else if (color == Brushes.Yellow) return 4;
            else if (color == Brushes.Brown) return 5;
            else return -1;
        }
    }
}

Как вы можете видеть, там есть специальные конвертеры, некоторые объекты, которые мы используем для создания дополнительных функциональных возможностей UX и обрабатывают взаимодействие команд непосредственно из View.

Задача (называемая здесь Tarefa от португальских имен) сама является Entity Framework Self Tracking Entity, и она расширяется путем добавления второго частичного определения класса. Сам шаблон STE T4 не так уж и доработан, большая часть настройки выполняется путем добавления дополнительного частичного определения класса к объектам, которые мы хотим настроить.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sigep.Common.DataSelfTracking.Utils;

namespace Sigep.Common.DataSelfTracking
{
    [System.Diagnostics.DebuggerDisplay("{Codigo}")]
    public partial class Tarefa
    {
        private const double GRID_HEIGHT = 32;

        public Actividade ActividadeNum 
        {
            get 
            {
                return Actividade;
            }
            set
            {
                Actividade = value;
                this.Numero = this.Actividade.Tarefas.Max(X=> X.Numero)+1;
            }
        }


        public Candidatura CandidaturaActual
        {
            get
            {
                if (RelsRecursosInternosTarefas.Count > 0)
                    return this.RelsRecursosInternosTarefas[0].RecursoInterno.Estabelecimento.Promotor.Candidatura;
                else if (RelsRecursosExternosTarefas.Count > 0)
                    return this.RelsRecursosExternosTarefas[0].RecursoExterno.EntidadeExterna.Promotores[0].Candidatura;
                else
                    return null;
            }
        }

        public double TotalHoras
        {
            get
            {
                return RelsRecursosInternosTarefas.Sum(r => r.Duracao) + RelsRecursosExternosTarefas.Sum(r => r.Duracao);
            }
        }

        public string Codigo
        {
            get
            {
                return Actividade.PKID.ToString() + "." + Numero;
            }
        }

        public int DuracaoDias
        {
            get
            {
                return (int)Math.Ceiling(DateTimeUtils.CalculateBusinessDays(Inicio, Fim));
            }
        }

        public IEnumerable<AlocacaoPromotorTarefa> PercentagensParticipacao
        {
            get
            {
                var altura = GRID_HEIGHT / CandidaturaActual.Promotores.Count;

                int index = 0;
                foreach (var promotor in CandidaturaActual.Promotores.OrderBy(p => p.Nome))
                {
                    var totalRI = RelsRecursosInternosTarefas.Where(r => r.RecursoInterno.Estabelecimento.Promotor == promotor).Sum(r => r.Duracao);
                    var totalRE = RelsRecursosExternosTarefas.Where(r => r.RecursoExterno.Estabelecimento.Promotor == promotor).Sum(r => r.Duracao);

                    yield return new AlocacaoPromotorTarefa() {
                        Actual = totalRI + totalRE,
                        Restante = TotalHoras - totalRI - totalRE,
                        Index = index++,
                        GridHeight = altura
                    };
                }
            }
        }

        public DateTime GetPrimeiroDiaAno(int ano)
        {
            if (ano < Inicio.Year || ano > Fim.Year) throw new Exception("Ano Invalido");
            else if (Inicio.Year == ano) return Inicio;
            else return new DateTime(ano, 1, 1);
        }

        public DateTime GetUltimoDiaAno(int ano)
        {
            if (ano < Inicio.Year || ano > Fim.Year) throw new Exception("Ano Invalido");
            else if (Fim.Year == ano) return Fim;
            else return new DateTime(ano, 12, 31);
        }

        public int GetDuracaoDias(int ano)
        {
            if (ano < Inicio.Year || ano > Fim.Year) return 0;
            else if (ano == Inicio.Year && ano == Fim.Year) return (int)DateTimeUtils.CalculateBusinessDays(Inicio, Fim) + 1;
            else if (ano == Inicio.Year) return (int)DateTimeUtils.CalculateBusinessDays(Inicio, new DateTime(ano, 12, 31)) + 1;
            else if (ano == Fim.Year) return (int)DateTimeUtils.CalculateBusinessDays(new DateTime(ano, 1, 1), Fim) + 1;
            else return (int)DateTimeUtils.CalculateBusinessDays(new DateTime(ano, 1, 1), new DateTime(ano, 12, 31));
        }

        public double GetDuracaoMeses(int ano)
        {
            if (ano < Inicio.Year || ano > Fim.Year) return 0;
            else if (ano == Inicio.Year && ano == Fim.Year) return DateTimeUtils.CalculateMonths(Inicio, Fim);
            else if (ano == Inicio.Year) return DateTimeUtils.CalculateMonths(Inicio, new DateTime(ano, 12, 31));
            else if (ano == Fim.Year) return DateTimeUtils.CalculateMonths(new DateTime(ano, 1, 1), Fim);
            else return 12;
        }
    }

    public class AlocacaoPromotorTarefa
    {
        public double Actual { get; set; }
        public double Restante { get; set; }
        public int Index { get; set; }
        public double GridHeight { get; set; }
    }
}

Как вы можете видеть, это в основном геттеры, которые возвращают то, что мы хотим привязать к представлению напрямую, что не является частью модели. Эта практика делает разработку очень быстрой, поскольку написание конвертеров может быть довольно сложным для некоторых типов экспозиций.

Это много кода, но, надеюсь, даст вам представление о том, что писать в ViewModel и что писать в модели, как расширять модель и как вы связываете вещи вокруг.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...