在MVVM中封装模型

本文关键字:模型 封装 MVVM | 更新日期: 2023-09-27 18:21:09

这个问题最初是在代码评审中开始的,所以为了避免重复,我不会把它全部粘贴在这里。因此,在我收到第一个问题的答案后,我还有另一个问题:
我应该如何包装我的business object(用作Entity Framework Code First model),以便我可以包括一些仅与特定模型相关的属性,以及我应该如何从ViewModel中公开它?如何将更改保存回原始model

编辑:
仍在与此作斗争,因此我添加了一个可复制的小示例:

//class used as an EF code first model
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual ObservableCollection<Order> Orders { get; set; }
}

Person在一个名为PersonManagerWindow 的窗口中进行管理

//view model for the PersonManagerWindow
public class PersonManagerViewModel : ObservableObject
{
    private string _personName;
    public string PersonName
    {
        get { return _personName; }
        set
        {
            _personName = value;
            SelectedPerson = null;
            RaisePropertyChanged();
        }
    }

    private Person _selectedPerson;
    public Person SelectedPerson
    {
        get { return _selectedPerson; }
        set
        {
            _selectedPerson = value;
            RaisePropertyChanged();
        }
    }
}

我的问题是:

  • SelectedPerson应该公开"真实"的Person对象吗为它创建视图模型
  • 如果ViewModel是首选,我应该如何使用它Entity-Framework访问

在MVVM中封装模型

我认为您不能更改的原始型号

在我的一个项目中,我用T4模板增强了原始模型。类(由生成器)进行修改,但不修改源文件。

例如,这是原始代码

public partial class ImprimanteSNData
{
    [T4Order(2)]
    private Boolean isConnected;
    [T4Order(0)]
    private List<String> printer;
    [T4Order(1)]
    private String serialNumber;
}

到T4时,该级别增强了:

生成的代码:

[DataContract]
public partial class ImprimanteSNData : IExtensibleDataObject
{
    private ExtensionDataObject extensionDataObjectValue;
    public ExtensionDataObject ExtensionData
    {
        get { return this.extensionDataObjectValue; }
        set { this.extensionDataObjectValue = value; }
    }
    [DataMember(Order = 2)]
    public System.Boolean IsConnected
    {
        get 
        {
            return this.isConnected;
        }
        set 
        {
            this.isConnected = value;
        }
    }
    [DataMember(Order = 0)]
    public System.Collections.Generic.List<System.String> Printer
    {
        get 
        {
            return this.printer ?? new System.Collections.Generic.List<System.String>();
        }
        set 
        {
            this.printer = value;
        }
    }
    [DataMember(Order = 1)]
    public System.String SerialNumber
    {
        get 
        {
            return this.serialNumber == null ? String.Empty : this.serialNumber.Trim();
        }
        set 
        {
            this.serialNumber = value;
        }
    }
}

所有这些第二部分都是从第一部分完全生成的。因此,编写新类和修改getter函数非常简单。

T4代码:

<#@ template language="C#" debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="System.Xml.dll" #>
<#@ Assembly Name="System.Xml.Linq.dll" #>
<#@ Assembly Name="System.Windows.Forms.dll" #>
<#@ Assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Runtime.Serialization" #>
<#@ import namespace="XXXXXXXXXXX" #>
<#@ import namespace="System.Runtime.Serialization" #>
<#@ Assembly Name="$(ProjectDir)'bin'$(ConfigurationName)'FicheSignaletiqueViseoData.dll" #>
// T4Class : Génération des accesseurs de toutes les classes de FicheSignaletiqueViseoData
// pour les types non génériques et les classes, on rajoute une protection contre le null
// pour les String, on rajoute une protection contre le null et un Trim automatique
// Date de génération : <#= System.DateTime.Now.ToString() #>
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
<#   
    const String BusinessEntityNamespace= "XXXXXXXXXXX";
    WriteLine("namespace {0}", BusinessEntityNamespace);
    WriteLine("{");
    PushIndent("'t");
    String T4TemplatePath = Path.GetDirectoryName(Host.TemplateFile);
    String dataContractSource = Path.Combine(T4TemplatePath, "DataContract");
    String[] sources = Directory.GetFiles(dataContractSource, "*.cs");
    IServiceProvider hostServiceProvider = (IServiceProvider)Host;
    EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
    foreach(string file in sources)
    {
        EnvDTE.ProjectItem projectItem = dte.Solution.FindProjectItem(file);
        FileCodeModel fileCodeModel = projectItem.FileCodeModel;
        if (fileCodeModel != null)
        {
            foreach (CodeElement codeElement in fileCodeModel.CodeElements)
            {
                if (codeElement is CodeNamespace)
                {
                    CodeNamespace nsp = codeElement as CodeNamespace;
                    foreach (CodeElement subElement in nsp.Children)
                    {
                        if (subElement is CodeClass)
                        {
                            CodeClass classe = subElement as CodeClass;
                            if (classe.Access == vsCMAccess.vsCMAccessPublic && classe.Name.StartsWith("T4") == false && classe.Name != "Important")
                            {
                                GenerateClassFromCode(classe);
                            }
                        }
                    }
                }
            }
        }
    }
    PopIndent();
    WriteLine("}");
#>

<#+  
    private void GenerateClassFromCode(CodeClass classToGenerate)
    {
        WriteLine("[DataContract]");
        WriteLine("public partial class {0} : IExtensibleDataObject", classToGenerate.Name);
        WriteLine("{");
        PushIndent("'t");
        WriteLine("private ExtensionDataObject extensionDataObjectValue;");
        WriteLine(String.Empty);
        WriteLine("public ExtensionDataObject ExtensionData");
        WriteLine("{");
        PushIndent("'t");
        WriteLine("get { return this.extensionDataObjectValue; }");
        WriteLine("set { this.extensionDataObjectValue = value; }");
        PopIndent();
        WriteLine("}"); 
        WriteLine(String.Empty);
        List<Tuple<string, Int16>> checkOrder = new List<Tuple<string, Int16>>();
        List<CodeVariable> listVariable = new List<CodeVariable>();
        List<CodeEnum> listEnum = new List<CodeEnum>();
        foreach (CodeElement elem in classToGenerate.Members)
        {
            if (elem is CodeVariable)
            {
                listVariable.Add(elem as CodeVariable);
            }
            if (elem is CodeEnum)
            {
                listEnum.Add(elem as CodeEnum);
            }
        }
        foreach(CodeVariable variable in listVariable)
        {
            if (variable.Access == vsCMAccess.vsCMAccessPrivate)
            {
                // attributs
                foreach (CodeAttribute attribut in variable.Attributes)
                {
                    Int16 order = getPropertyOrderFromCode(attribut);
                    if (order >= 0)
                    {
                        if (checkOrder.Where(c => c.Item2 == order).Count() > 0)
                        {
                            WriteLine("// Ci dessous, erreur de compilation voulue. Veuillez corriger et recompiler.");
                            WriteLine("ERROR : Dans la classe " + classToGenerate.Name + ", doublon sur le T4Order " + order + " (utilisé par " + checkOrder.Where(c => c.Item2 == order).First().Item1 + ")");
                        }
                        else
                        {
                            WriteLine("[DataMember(Order = {0})]", order);
                            checkOrder.Add(new Tuple<string, Int16>(variable.Name, order));
                        }
                    }
                    else
                    {
                        Write("[" + attribut.FullName);
                        if(attribut.Children != null && attribut.Children.Count > 0)
                        {
                            TextPoint start = attribut.Children.Cast<CodeElement>().First().GetStartPoint();
                            TextPoint finish = attribut.GetEndPoint();
                            String allArguments = start.CreateEditPoint().GetText(finish);
                            Write("(" + allArguments);
                        }
                        WriteLine("]");   
                    }                   
                }
                // variable
                string propertyTypeStr = variable.Type.AsFullName;
                Type type = Type.GetType(propertyTypeStr);
                WriteLine("public {0} {1}", propertyTypeStr, MakeUpper(variable.Name));
                WriteLine("{");
                PushIndent("'t");
                    // getter
                    WriteLine("get ");
                    WriteLine("{");
                    PushIndent("'t");
                        if (propertyTypeStr == "System.String")
                        {
                            WriteLine("return this.{0} == null ? String.Empty : this.{0}.Trim();", variable.Name, propertyTypeStr);
                        }
                        else if (listEnum.Select(e => e.FullName).Contains(propertyTypeStr))
                        {
                            WriteLine("return this.{0};", variable.Name);
                        }
                        else if (type == null || ((type.IsGenericType || type.IsClass)))
                        {
                            WriteLine("return this.{0} ?? new {1}();", variable.Name, propertyTypeStr);
                        }
                        else
                        {
                            WriteLine("return this.{0};", variable.Name);
                        }
                    PopIndent();
                    WriteLine("}");     
                    WriteLine(String.Empty);
                    // setter
                    WriteLine("set ");
                    WriteLine("{");
                    PushIndent("'t");
                        WriteLine("this.{0} = value;", variable.Name);
                    PopIndent();
                    WriteLine("}"); 
                PopIndent();
                WriteLine("}"); 
                WriteLine(String.Empty);
            }
        }
        PopIndent();
        WriteLine("}");
        WriteLine(String.Empty);
    }
    private Int16 getPropertyOrderFromCode(CodeAttribute member)
    {
        if (member != null)
        {
            if (member.FullName == typeof(T4Order).ToString())
            {
                return Convert.ToInt16(member.Value);
            }
        }
        return -1;
    }
    private String MakeUpper(String name)
    {
        return name.Substring(0, 1).ToUpper() + name.Substring(1, name.Length-1);
    }
#>

我还使用了一个属性(棘手的是,我可以标记原始代码中的私有属性,以使生成的代码中的公共属性上的DataOrder)

public class T4Order : Attribute
{
    private Int16 order;
    public T4Order(Int16 o)
    {
        this.order = o;
    }
    public Int16 Order
    {
        get { return this.order; }
        set { this.order = value; }
    }
}

这只是一个例子,但它表明,在不修改原始代码的情况下,可以用T4做很多事情。