我目前正在开发一个应用程序,其所有实体都来自Linq2SQL。试图找到一种方法来保持所有内容完全分离,我为Linq2SQL添加了一个域模型,并用ViewModel包装它。
这为我的代码增加了一点复杂性,因为我有一个服务层,在初始化时我从L2S获得了实体,新建了我的域类,并用实体中的任何数据填充它。
当我想将项目重新插入L2S中的数据库时,我遇到了一个问题,这使我做了相反的事情:使用域类中的数据填充实体。在这一点上,我开始怀疑自己是否走在正确的道路上,所以我开始思考我做错了什么,或者只是认为是正确的方法,但最终却没有。
我最终认为包装实体而不是填充域模型并包装 ,这可能是在这种情况下正确的方法。如果我不这样做,我需要在我的View的ViewModel中有一个指向DAL的using语句。也许我错了,但据我所读(在书籍,网络文章中),这不是一个清晰的关注点分离。
因此,我的问题是:
当没有域模型时,模型的ViewModel是否用于将DAL保持在View的ViewModel之外?
答案 0 :(得分:1)
你的问题有点模糊。但是我看到很多关于MVVM模式的混乱。很多人在ViewModel中“包装”模型,就像你正在做的那样,这是错误的。
模型基本上就是你的L2S对象。
ViewModel直接公开L2S对象,并处理交互逻辑,即处理命令,对象更新等(不包装任何东西,只是作为链接到它们)。
Wraping是错误的,例如,假设你想要一个对于转换器来说过于复杂的东西,但是你需要在你的Entity(BusinessObject或来自L2S的任何东西 - 你的ORM框架)上拥有它,你应该扩展你的实体支持这一点。
我会给你一些例子,但这些例子来自略有不同的架构:
这是此应用程序中的任务列表,最终结果是带有甘特图的项目视图。
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)本身是一个实体框架自我跟踪实体,它通过添加第二个部分类定义进行扩展。 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; }
}
}
正如您所看到的,它主要是getter返回我们想要直接绑定到View的东西,而不是Model的一部分。这种做法使得开发速度非常快,因为对于某些类型的曝光,写入转换器可能非常复杂。
这是很多代码,但希望能让您了解在ViewModel上写什么以及在Model上写什么,如何扩展Model以及如何绑定东西。