C# 是否有一种方法可以自动创建ViewModel属性或将其映射到模型属性?

C# 是否有一种方法可以自动创建ViewModel属性或将其映射到模型属性?,c#,wpf,mvvm,dry,C#,Wpf,Mvvm,Dry,我有以下模型/视图模型对。这是一种非常常见的情况—从ViewModel到模型属性的纯映射—并且包含大量重复且容易出错的代码 我想知道是否有更好的方法来做到这一点,特别是减少出错的机会(忘记属性,使用错误的属性名称) 欢迎使用较新的语言功能,如CallingMemberName,但目前我不确定是否理解它们 公共类参数Geometricsviewmodel:ConfiguracoesViewModel { // (...) 公共双距离广播电台 { get=>Model.distanceaproj

我有以下模型/视图模型对。这是一种非常常见的情况—从ViewModel到模型属性的纯映射—并且包含大量重复且容易出错的代码

我想知道是否有更好的方法来做到这一点,特别是减少出错的机会(忘记属性,使用错误的属性名称)

欢迎使用较新的语言功能,如
CallingMemberName
,但目前我不确定是否理解它们



公共类参数Geometricsviewmodel:ConfiguracoesViewModel
{
// (...)
公共双距离广播电台
{
get=>Model.distanceaprojectorparede;
设置
{
Model.distanceaprojectorparede=值;
RaisePropertyChanged(()=>距离APROJETORPAREDE);
}
}
公共双助听器
{
get=>Model.AlturaProjetor;
设置
{
Model.AlturaProjetor=值;
RaisePropertyChanged(()=>AlturaProjetor);
}
}
公共双备选方案
{
get=>Model.alturasuberiorprojecao;
设置
{
Model.alturasuberiorprojecao=值;
RaisePropertyChanged(()=>ALTURABERIOR PROJECAO);
}
}
公共双AlturaSuperiorProjecao
{
get=>Model.AlturaSuperiorProjecao;
设置
{
Model.AlturaSuperiorProjecao=值;
RaisePropertyChanged(()=>AlturaSuperiorProjecao);
}
}
公共双重距离
{
get=>Model.distanceacameraparede;
设置
{
Model.distance acameraparede=值;
RaisePropertyChanged(()=>DistanceAcameraparede);
}
}
公共双Altura照相机
{
get=>Model.AlturaCamera;
设置
{
Model.AlturaCamera=值;
RaisePropertyChanged(()=>AlturaCamera);
}
}
公共双重替代
{
get=>Model.alturasuberiorImagem;
设置
{
Model.alturasuberiorImagem=值;
RaisePropertyChanged(()=>Altura-SuberiorImagem);
}
}
公共双AlturaSuperiorImagem
{
get=>Model.AlturaSuperiorImagem;
设置
{
Model.AlturaSuperiorImagem=值;
RaisePropertyChanged(()=>AlturaSuperiorImagem);
}
}
}

听起来您正在寻找类似这样的东西

没有必要将ViewModel编写为模型顶部的外观

直接或使用库(如)更改模型实现InotifyProperty。然后将整个模型发布为ViewModel的单个属性,并绑定到视图中的属性


我在我的博客中介绍了这个主题-/。

如果使用生成的代码可以,那么可以使用它生成ViewModel中的所有属性。创建用于保存模型类型的属性:

[AttributeUsage(AttributeTargets.Class)]
public class ViewsAttribute : Attribute {
    public ViewsAttribute(Type type) {

    }
}
将此添加到VM,并使其成为部分:

[Views(typeof(ParametrosGeometricos))]
partial class ParametrosGeometricosViewModel {
    (...)
}
以下T4是我使用的简短版本,但我相信有更好的方法,因为我不是专家:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="$(SolutionDir)\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll" #>
<#@ assembly name="$(SolutionDir)\packages\System.Collections.Immutable.1.3.1\lib\netstandard1.0\System.Collections.Immutable.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.Common.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.CSharp.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="System.Runtime" #>
<#@ assembly name="System.Text.Encoding" #>
<#@ assembly name="System.Threading.Tasks" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<# 
    var solutionPath = Host.ResolveAssemblyReference("$(ProjectDir)");
    var files = Directory.GetFiles(solutionPath,"*.cs",SearchOption.AllDirectories);

    IEnumerable<ClassDeclarationSyntax> syntaxTrees = files.Select(x => CSharpSyntaxTree.ParseText(File.ReadAllText(x))).Cast<CSharpSyntaxTree>().SelectMany(c => c.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>());

    foreach(ClassDeclarationSyntax declaration in syntaxTrees.Where(x => (x.AttributeLists != null && x.AttributeLists.Count > 0 && x.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).Any()))) {
        SyntaxNode namespaceNode = declaration.Parent;
        Write("\n\n");

        while(namespaceNode != null && !(namespaceNode is NamespaceDeclarationSyntax)) {
            namespaceNode = namespaceNode.Parent;
        }

        if(namespaceNode != null) {
            WriteLine("namespace " + ((NamespaceDeclarationSyntax)namespaceNode).Name.ToString() + " {");
        }

        string modelName= declaration.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).First().ArgumentList.Arguments.ToString();
        modelName = modelName.Substring(7, modelName.Length-8);

        ClassDeclarationSyntax modelClass = syntaxTrees.Where(x => x.Identifier.ToString() == modelName).First();

        WriteLine("    public partial class " + declaration.Identifier.Text + " {");

        foreach(PropertyDeclarationSyntax prp in modelClass.DescendantNodes().OfType<PropertyDeclarationSyntax>()){
            WriteLine("        public " + prp.Type + " " + prp.Identifier + " {");
            WriteLine("            get => Model." + prp.Identifier + ";");
            WriteLine("            set");
            WriteLine("            {");
            WriteLine("                Model." + prp.Identifier + " = value;");
            WriteLine("                RaisePropertyChanged(() => " + prp.Identifier + ");");
            WriteLine("            }");
            WriteLine("        }\n");
        }

        WriteLine("    }");

        if(namespaceNode != null) {
            Write("}");
        }
    }
#>

您可以添加很多更改,例如,不使用自定义属性,而是为继承自
ConfiguracoesViewModel
的所有类生成代码。您还可以检查每个属性是否已经添加到VM中,而不是生成它们,这允许您通过简单地将其添加到类中来为所需的属性创建自定义getter和setter。

我偶然发现了这个问题,所以我想我应该添加如何解决这个问题的方法。我有一个
MyViewModelBase
类型来包装我的
MyModel
。这两个类都实现了INotifyPropertyChanged,ViewModel只是转发PropertyChanged事件,如下所示:

公共类MyViewModelBase:INotifyPropertyChanged
{
公共财产
{
get=>\u model.MyProperty;
set=>\u model.MyProperty=value;
}
公共MyViewModelBase(MyModel模型)
{
//我们将包装器属性命名为与模型相同的名称,
//在这里,我们只是转发属性更改通知
model.PropertyChanged+=(发送方,e)=>PropertyChanged?.Invoke(this,e);
}
...
}
公共类MyModel:INotifyPropertyChanged
{
//我们用fody来筹集财产,
//但在这里可以正常升高,否则
公共int MyProperty{get;set;}
}
我们有许多不同的模型和视图模型继承自这两个基类。要获取模型中某个属性的更改通知,只需在视图模型中为其添加一个同名的包装器,当模型中的属性更改时,更改也将通过视图模型传播

请注意,我们在应用程序的有限部分中使用它,因为它非常适合。我不认为它可以扩展到大型应用程序的每个部分。在合适的地方使用它

现在,当谈到为基础模型自动生成包装器属性时,最好的选择(AFAIK)是Fody。我迅速看了看,发现了这个:。我不确定你是否可以用它,但这是我能找到的最接近的东西


当C#9/.NET 5发布时,源代码生成器也可以是一个选项。

AutoMapper?你知道吗?为什么不直接在(DTO?)模型中实现
INotifyPropertyChanged
?@dymanoid最终这是一个偏好问题。我喜欢将模型视为一个简单的属性包,VIEW模型具有通知属性更改的特性——在这种情况下,它用来触发自动保存到一个持久化的文件。从我理解的AutoMaPad主页中,您可以使用它来将一个对象实例映射到另一个对象实例,而我的用例是(双向)将对象(模型)中给定属性的值映射到
[Views(typeof(ParametrosGeometricos))]
partial class ParametrosGeometricosViewModel {
    (...)
}
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="$(SolutionDir)\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll" #>
<#@ assembly name="$(SolutionDir)\packages\System.Collections.Immutable.1.3.1\lib\netstandard1.0\System.Collections.Immutable.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.Common.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(SolutionDir)\packages\Microsoft.CodeAnalysis.CSharp.2.8.2\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="System.Runtime" #>
<#@ assembly name="System.Text.Encoding" #>
<#@ assembly name="System.Threading.Tasks" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<# 
    var solutionPath = Host.ResolveAssemblyReference("$(ProjectDir)");
    var files = Directory.GetFiles(solutionPath,"*.cs",SearchOption.AllDirectories);

    IEnumerable<ClassDeclarationSyntax> syntaxTrees = files.Select(x => CSharpSyntaxTree.ParseText(File.ReadAllText(x))).Cast<CSharpSyntaxTree>().SelectMany(c => c.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>());

    foreach(ClassDeclarationSyntax declaration in syntaxTrees.Where(x => (x.AttributeLists != null && x.AttributeLists.Count > 0 && x.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).Any()))) {
        SyntaxNode namespaceNode = declaration.Parent;
        Write("\n\n");

        while(namespaceNode != null && !(namespaceNode is NamespaceDeclarationSyntax)) {
            namespaceNode = namespaceNode.Parent;
        }

        if(namespaceNode != null) {
            WriteLine("namespace " + ((NamespaceDeclarationSyntax)namespaceNode).Name.ToString() + " {");
        }

        string modelName= declaration.AttributeLists.SelectMany(y => y.Attributes.Where(z=> z.Name.ToString()=="Views")).First().ArgumentList.Arguments.ToString();
        modelName = modelName.Substring(7, modelName.Length-8);

        ClassDeclarationSyntax modelClass = syntaxTrees.Where(x => x.Identifier.ToString() == modelName).First();

        WriteLine("    public partial class " + declaration.Identifier.Text + " {");

        foreach(PropertyDeclarationSyntax prp in modelClass.DescendantNodes().OfType<PropertyDeclarationSyntax>()){
            WriteLine("        public " + prp.Type + " " + prp.Identifier + " {");
            WriteLine("            get => Model." + prp.Identifier + ";");
            WriteLine("            set");
            WriteLine("            {");
            WriteLine("                Model." + prp.Identifier + " = value;");
            WriteLine("                RaisePropertyChanged(() => " + prp.Identifier + ");");
            WriteLine("            }");
            WriteLine("        }\n");
        }

        WriteLine("    }");

        if(namespaceNode != null) {
            Write("}");
        }
    }
#>
namespace TTTTTest {
    public partial class ParametrosGeometricosViewModel {
        public double DistanciaProjetorParede {
            get => Model.DistanciaProjetorParede;
            set
            {
                Model.DistanciaProjetorParede = value;
                RaisePropertyChanged(() => DistanciaProjetorParede);
            }
        }

        public double AlturaProjetor {
            get => Model.AlturaProjetor;
            set
            {
                Model.AlturaProjetor = value;
                RaisePropertyChanged(() => AlturaProjetor);
            }
        }

        public double AlturaInferiorProjecao {
            get => Model.AlturaInferiorProjecao;
            set
            {
                Model.AlturaInferiorProjecao = value;
                RaisePropertyChanged(() => AlturaInferiorProjecao);
            }
        }

        public double AlturaSuperiorProjecao {
            get => Model.AlturaSuperiorProjecao;
            set
            {
                Model.AlturaSuperiorProjecao = value;
                RaisePropertyChanged(() => AlturaSuperiorProjecao);
            }
        }

        public double DistanciaCameraParede {
            get => Model.DistanciaCameraParede;
            set
            {
                Model.DistanciaCameraParede = value;
                RaisePropertyChanged(() => DistanciaCameraParede);
            }
        }

        public double AlturaCamera {
            get => Model.AlturaCamera;
            set
            {
                Model.AlturaCamera = value;
                RaisePropertyChanged(() => AlturaCamera);
            }
        }

        public double AlturaInferiorImagem {
            get => Model.AlturaInferiorImagem;
            set
            {
                Model.AlturaInferiorImagem = value;
                RaisePropertyChanged(() => AlturaInferiorImagem);
            }
        }

        public double AlturaSuperiorImagem {
            get => Model.AlturaSuperiorImagem;
            set
            {
                Model.AlturaSuperiorImagem = value;
                RaisePropertyChanged(() => AlturaSuperiorImagem);
            }
        }

    }
}