65.9K
CodeProject 正在变化。 阅读更多。
Home

Mo+ - 超级化 Entity Framework 以生成工作的多层应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (6投票s)

2013年8月4日

CPOL

7分钟阅读

viewsIcon

25872

downloadIcon

381

使用 Mo+ 生成一个可运行的应用程序,其中包含实体框架模型、OData 服务、视图模型客户端和 WPF 应用程序。

引言

我们致力于赋能软件开发人员,让他们能够将更多精力集中在创意上,而将精力投入到开发软件中的繁琐细节上。我们相信,将基于模板的代码生成方法发展为一种模型驱动、面向模型的实现方式,是管理高质量生成代码的最佳途径,这些代码能够符合您的最佳实践,并与您的自定义代码无缝集成。

面向对象程序员深知面向对象语言相比过程式语言在创建大型复杂软件方面的优势,如封装、可重用性、模块化、清晰度等。为何在代码生成方面不能获得同样的优势呢?您可以通过 Mo+ 实现!Mo+ 的代码生成方法是唯一一种基于模板、模型驱动、真正的面向模型,并且在最初代码生成的同时也关注代码维护的方法。

在本文中,我们将使用 Mo+ 生成一个可运行的多层应用程序,并利用实体框架。我们将使用 Northwind SQL Server 数据库,并通过 Mo+ 模板生成实体框架层、OData 服务层、客户端视图模型层以及 WPF 管理用户界面应用程序。生成的应用程序将无需任何自定义代码即可运行。

背景

Mo+ 的Mo+ 模型驱动编程语言和面向模型的开发 IDE **Mo+ Solution Builder** 在这篇 CodeProject 文章中已经介绍和解释。在这里,您可以获取 Mo+ 安装程序和示例包,用于生成本文中的多层应用程序。

Mo+ 开源技术可在 moplus.codeplex.com 上获取,包括最新的安装程序、源代码、示例包(模板库)以及其他材料。视频教程也在此网站上提供。Mo+ Solution Builder 还包含广泛的内置帮助。

如果您在阅读本文时使用 Mo+ Solution Builder,并且需要了解如何从数据库加载解决方案模型,请观看此关于 从 SQL Server 加载模型 的教程。您还可以观看 创建多层应用程序,该教程创建的应用程序与本文相同,以及 更新多层应用程序,该教程演示了此应用程序的一些更新场景。

创建应用程序

实体框架是一个有效的 ORM,它使 .NET 开发人员能够使用领域特定的对象来处理关系数据。它消除了开发人员通常需要编写的大部分数据访问代码。但是,由于实体框架模型与数据访问层项目绑定,您需要自己构建其他层来利用此框架。

借助 Mo+,您不仅可以生成和维护 实体框架 模型,还可以生成和维护一个利用实体框架的整个解决方案。使用 Northwind SQL Server 数据库,我们将创建一个可运行的应用程序,其中将包含实体框架数据访问层、OData 服务层、客户端视图模型层以及 WPF 管理用户界面应用程序。

要创建此应用程序,我们首先创建一个 Mo+ 解决方案模型。有关如何使用 Mo+ Solution Builder 创建解决方案和利用示例包中的模板的详细信息,请参阅上面的“背景”部分。我们的解决方案模型需要包含以下信息:

  • 基本解决方案信息,包括 `SolutionFile` 模板以生成您的整体解决方案。
  • 一个 `DatabaseSource`,提供 Northwind 数据库的连接信息。选择 `MDLSqlModel` SQL Server 规范模板从数据库加载模型。在这里,您可以完全控制模型的加载方式(实体、属性、关系信息等),以及这些元素应如何命名。
  • 一个 `Project`,指定要创建实体框架数据访问层。选择 `EntityFramework` 代码模板来创建和维护此层。
  • 一个 `Project`,指定要创建 OData 服务层。选择 `EFDataServices` 代码模板来创建和维护此层。此层利用实体框架层。
  • 一个 `Project`,指定要创建客户端视图模型层。选择 `VMEFDS` 代码模板来创建和维护此层。此层利用 OData 服务层。
  • 一个 `Project`,指定要创建 WPF 用户界面层。选择 `WPFUI` 代码模板来创建和维护此层。此层利用客户端视图模型层。

以下是解决方案模型文件(也已附加),其中包含所有信息的原始格式。您可以复制此解决方案并更改模板位置和数据库信息。

<?xml version="1.0" encoding="utf-16"?>
<Solution xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <SpecSourceName>Northwind</SpecSourceName>
    <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
    <SolutionName>Northwind</SolutionName>
    <Namespace>Northwind</Namespace>
    <OutputSolutionFileName>Northwind.sln</OutputSolutionFileName>
    <CompanyName>Northwind</CompanyName>
    <TemplatePath>C:\InCode\VS2010\SamplePacks\Sample_CSharp_SQLServer_MySQL_Xml\
      Templates\CSharp_VS2010\SolutionFile.mpt</TemplatePath><Copyright />
    <DatabaseSourceList>
        <DatabaseSource>
            <SpecSourceName>INCODE-1</SpecSourceName>
            <SpecificationSourceID>d9e761ca-b00b-4665-9232-572ac6d6df67</SpecificationSourceID>
            <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
            <TemplatePath>C:\InCode\VS2010\SamplePacks\
              Sample_CSharp_SQLServer_MySQL_Xml\Specifications\SQLServer\MDLSqlModel.mps</TemplatePath>
            <Order>1</Order>
            <SourceDbServerName>INCODE-1</SourceDbServerName>
            <SourceDbName>Northwind</SourceDbName>
            <DatabaseTypeCode>1</DatabaseTypeCode>
        </DatabaseSource>
    </DatabaseSourceList>
    <ProjectList>
        <Project>
            <Tags>DS </Tags>
            <ProjectID>42697636-e196-4e21-94f8-1e87590dcdf2</ProjectID>
            <ProjectName>EFDataServices</ProjectName>
            <Namespace>Northwind.DS</Namespace>
            <DbServerName>INCODE-1</DbServerName>
            <DbName>Northwind</DbName>
            <TemplatePath>C:\InCode\VS2010\SamplePacks\Sample_CSharp_
              SQLServer_MySQL_Xml\Templates\CSharp_VS2010\Project\EFDataServices.mpt</TemplatePath>
            <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
            <ProjectReferenceList>
                <ProjectReference>
                    <Tags>BLL </Tags>
                    <SpecSourceName>EntityFramework</SpecSourceName>
                    <ProjectID>42697636-e196-4e21-94f8-1e87590dcdf2</ProjectID>
                    <ReferencedProjectID>026127c5-dae8-4e71-8613-0c255adb4cf6</ReferencedProjectID>
                    <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
                </ProjectReference>
            </ProjectReferenceList>
        </Project>
        <Project>
            <Tags>BLL </Tags>
            <SpecSourceName>EntityFramework</SpecSourceName>
            <ProjectID>026127c5-dae8-4e71-8613-0c255adb4cf6</ProjectID>
            <ProjectName>EntityFramework</ProjectName>
            <Namespace>Northwind.EF</Namespace>
            <DbServerName>INCODE-1</DbServerName>
            <DbName>Northwind</DbName>
            <TemplatePath>C:\InCode\VS2010\SamplePacks\Sample_CSharp_SQLServer
              _MySQL_Xml\Templates\CSharp_VS2010\Project\EntityFramework.mpt</TemplatePath>
            <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
        </Project>
        <Project>
            <Tags>VM </Tags>
            <ProjectID>a7845b2f-6ed6-42c6-ab02-90720f2d4e5e</ProjectID>
            <ProjectName>VMEFDS</ProjectName>
            <Namespace>Northwind.VM</Namespace>
            <TemplatePath>C:\InCode\VS2010\SamplePacks\Sample_CSharp_SQLServer
              _MySQL_Xml\Templates\CSharp_VS2010\Project\VMEFDS.mpt</TemplatePath>
            <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
            <ProjectReferenceList>
                <ProjectReference>
                    <Tags>DS </Tags>
                    <SpecSourceName>EFDataServices</SpecSourceName>
                    <ProjectID>a7845b2f-6ed6-42c6-ab02-90720f2d4e5e</ProjectID>
                    <ReferencedProjectID>42697636-e196-4e21-94f8-1e87590dcdf2</ReferencedProjectID>
                    <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
                </ProjectReference>
            </ProjectReferenceList>
        </Project>
        <Project>
            <Tags />
            <ProjectID>7a3232ed-fe15-4df5-8589-cdcd60d5c065</ProjectID>
            <ProjectName>WPFUI</ProjectName>
            <Namespace>Northwind.UI</Namespace>
            <TemplatePath>C:\InCode\VS2010\SamplePacks\Sample_CSharp_SQLServer
              _MySQL_Xml\Templates\CSharp_VS2010\Project\WPFUI.mpt</TemplatePath>
            <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
            <ProjectReferenceList>
                <ProjectReference>
                    <Tags>VM </Tags>
                    <ProjectID>7a3232ed-fe15-4df5-8589-cdcd60d5c065</ProjectID>
                    <ReferencedProjectID>a7845b2f-6ed6-42c6-ab02-90720f2d4e5e</ReferencedProjectID>
                    <SolutionID>c51311ae-4f0c-41ce-9349-3ca1e23de6ec</SolutionID>
                </ProjectReference>
            </ProjectReferenceList>
        </Project>
    </ProjectList>
</Solution> 

下图显示了 Mo+ Solution Builder 中树状视图的解决方案模型信息。

设置好此解决方案后,您可以使用 Mo+ Solution Builder 生成应用程序,并在数据库架构发生更改时自动更新它。下图显示了从 Northwind 数据库加载的解决方案模型中的一些 `Entity` 和 `Property` 信息。

下图显示了正在运行的已生成 WPF 应用程序的视图。

查看模板和代码示例

我们匆匆浏览了此可运行应用程序的创建细节。我们将查看与每个层中的 `Customer` 对象相关的模板代码和输出解决方案代码的示例。如果您想深入了解,请在 Mo+ Solution Builder 中加载模板。您也可以在调试器中运行模板,并检查过程中发生的情况。

实体框架层

Mo+ 解决方案模型包含生成和维护实体框架模型所需的所有信息。这仅仅是从一个模型到另一个模型的翻译过程。

例如,`EntityFramework` 代码模板会调用 `EFModelMarkupCode` 模板来创建实体框架模型标记(edmx)文件。该模板中的以下代码块显示了如何创建实体框架概念模型。

<%%-<!-- SSDL content -->
    <edmx:ConceptualModels>
      <Schema Namespace="%%><%%=Project.DbName%%><%%-Model" 
           Alias="Self" 
           xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 
           xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
        <EntityContainer Name="%%><%%=DSModelClassName%%><%%-" 
            annotation:LazyLoadingEnabled="true">%%>
foreach (Entity in Solution where Tags.Contains("DB") == true)
{
    <%%-
          <EntitySet Name="%%><%%=BLLPluralEntityName%%><%%-" 
            EntityType="%%><%%=Project.DbName%%><%%-Model.%%>
            <%%=BLLClassName%%><%%-" />%%>
}
foreach (Relationship in Solution where Tags.Contains("DB") == true)
{
    <%%-
          <AssociationSet Name="%%><%%=RelationshipName%%><%%-" 
            Association="%%><%%=Project.DbName%%><%%-Model.%%>
                   <%%=RelationshipName%%><%%-">%%>
              <%%-
            <End Role="%%><%%=EFSinkRoleName%%><%%-" 
              EntitySet="%%><%%=ReferencedEntity.BLLPluralEntityName%%><%%-" />%%>
          <%%-
            <End Role="%%><%%=EFSourceRoleName%%><%%-" 
              EntitySet="%%><%%=Entity.BLLPluralEntityName%%><%%-" />%%>
           <%%-
          </AssociationSet>%%>
}
<%%-
        </EntityContainer>%%>
foreach (Entity in Solution where Tags.Contains("DB") == true)
{
<%%-
        <EntityType Name="%%><%%=BLLClassName%%><%%-">
          <Key>%%>
          foreach (Property where Tags.Contains("DB") == true && IsPrimaryKeyMember == true)
          {
              <%%-
            <PropertyRef Name="%%><%%=BLLPropertyName%%><%%-" />%%>
           }
            <%%-
          </Key>%%>
          foreach (Property where Tags.Contains("DB") == true)
          {
          <%%-
          <Property Name="%%><%%=BLLPropertyName%%><%%-" Type="%%>
            <%%=CSharpBaseDataType.Replace("Byte[]", "Binary")%%><%%-"%%>
              if (IsNullable == false)
              {
              <%%- Nullable="false"%%>
              }
              if (DBDataType != DBBaseDataType)
              {
              <%%- MaxLength="%%><%%=Length%%><%%-"%%>
              }
              // TODO: determine overall rules to determine StoreGeneratedPattern and FixedLength
              if (DataTypeCode == 25 /* Timestamp */)
              {
              <%%- MaxLength="8" FixedLength="true" 
                     annotation:StoreGeneratedPattern="Computed"%%>
              }
          <%%- />%%>
          }
          
        with (BaseEntity)
        {
            CurrentRelationship = null
            foreach (Relationship in ../Entity)
            {
                if (CurrentRelationship == null)
                {
                    if (Relationship.ReferencedEntityID == ../EntityID)
                    {
                        log("EFClassCode", "IsBaseRelationship", true)
                        foreach (RelationshipProperty)
                        {
                            with (Property from Solution.Find(PropertyID))
                            {
                                if (IsPrimaryKeyMember == false)
                                {
                                         log("EFClassCode", "IsBaseRelationship", false)
                               }
                            }
                        }
                        if (LogValue("EFClassCode", "IsBaseRelationship") == true)
                        {
                            CurrentRelationship = Relationship
                        }
                    }
                }
            }
            if (CurrentRelationship != null)
            {
                with (CurrentRelationship)
                {
              <%%-
          <NavigationProperty Name="%%><%%=ReferencedEntity.BLLClassName%%><%%-" 
            Relationship="%%><%%=Project.DbName%%><%%-Model.%%><%%=
            RelationshipName%%><%%-" FromRole="%%><%%=Entity.BLLClassName%%>
            <%%-" ToRole="%%><%%=ReferencedEntity.BLLClassName%%><%%-" />%%>
                }
            }
        }
        foreach (Entity in Solution where BaseEntityID == ../EntityID)
        {
            CurrentRelationship = null
            foreach (Relationship)
            {
                if (CurrentRelationship == null)
                {
                    if (Relationship.ReferencedEntityID == ../../EntityID)
                    {
                        log("EFClassCode", "IsBaseRelationship", true)
                        foreach (RelationshipProperty)
                        {
                            with (Property from Solution.Find(ReferencedPropertyID))
                            {
                                if (IsPrimaryKeyMember == false)
                                {
                                         log("EFClassCode", "IsBaseRelationship", false)
                               }
                            }
                        }
                        if (LogValue("EFClassCode", "IsBaseRelationship") == true)
                        {
                            CurrentRelationship = Relationship
                        }
                    }
                }
            }
            if (CurrentRelationship != null)
            {
                with (CurrentRelationship)
                {
              <%%-
          <NavigationProperty Name="%%><%%=Entity.BLLClassName%%><%%-" 
            Relationship="%%><%%=Project.DbName%%><%%-Model.%%><%%=
            RelationshipName%%><%%-" FromRole="%%><%%=ReferencedEntity.
            BLLClassName%%><%%-" ToRole="%%><%%=Entity.
            BLLClassName%%><%%-" />%%>
                }
            }
        }
        foreach (EntityReference)
        {
            CurrentRelationship = null
            foreach (PropertyRelationship limit 1)
            {
                with (Relationship)
                {
                    CurrentRelationship = Relationship
                }
            }
            if (CurrentRelationship != null)
            {
              <%%-
          <NavigationProperty Name="%%><%%=BLLPropertyName%%>
                with (CurrentRelationship)
                {
          <%%-" Relationship="%%><%%=Project.DbName%%><%%-Model.%%>
            <%%=RelationshipName%%><%%-" FromRole="%%><%%=
            Entity.BLLClassName%%><%%-" ToRole="%%>
            <%%=ReferencedEntity.BLLClassName%%><%%-" />%%>
                }
            }
        }
        foreach (Collection)
        {
             CurrentRelationship = null
            foreach (PropertyRelationship limit 1)
            {
                 if (Relationship.ReferencedEntityID == ../EntityID && 
                       Relationship.EntityID == ../ReferencedEntityID)
                       // must limit collections in EF to direct relationships
                {
                    with (Relationship)
                    {
                        CurrentRelationship = Relationship
                    }
                }
            }
            if (CurrentRelationship != null)
            {
              <%%-
          <NavigationProperty Name="%%><%%=BLLPropertyName%%>
                with (CurrentRelationship)
                {
          <%%-" Relationship="%%><%%=Project.DbName%%><%%-Model.
            %%><%%=RelationshipName%%><%%-" FromRole="%%>
            <%%=ReferencedEntity.BLLClassName%%><%%-" ToRole="
            %%><%%=Entity.BLLClassName%%><%%-" />%%>
                }
            }
        }
          <%%-
        </EntityType>%%>
}
foreach (Relationship in Solution where Tags.Contains("DB") == true)
{
    <%%-
        <Association Name="%%><%%=RelationshipName%%><%%-">
          <End Role="%%><%%=EFSinkRoleName%%><%%-" Type="%%>
            <%%=Project.DbName%%><%%-Model.%%><%%=
            ReferencedEntity.BLLClassName%%><%%-" Multiplicity="%%>
          if (ReferencedItemsMax == 1)
          {
              if (ReferencedItemsMin == 0)
              {
                  <%%-0..1%%>
              }
              else
              {
                  <%%-1%%>
              }
          }
          else
          {
              <%%-*%%>
          }
          <%%-" />
          <End Role="%%><%%=EFSourceRoleName%%><%%-" 
            Type="%%><%%=Project.DbName%%><%%-Model.%%><
            %%=Entity.BLLClassName%%><%%-" Multiplicity="%%>
          if (ItemsMax == 1)
          {
              if (ItemsMin == 0)
              {
                  <%%-0..1%%>
              }
              else
              {
                  <%%-1%%>
              }
          }
          else
          {
              <%%-*%%>
          }
          <%%-" />
          <ReferentialConstraint>
            <Principal Role="%%><%%=ReferencedEntity.BLLClassName%%><%%-">%%>
            foreach (RelationshipProperty)
            {
                    // apparently the referential constraints can only handle primary keys, not unique indexes
                    if (ReferencedProperty.IsPrimaryKeyMember == true)
                    {
                        with (ReferencedProperty)
                        {
                    <%%-
              <PropertyRef Name="%%><%%=BLLPropertyName%%><%%-" />%%>
                     }
            }
            }
             <%%-
            </Principal>
            <Dependent Role="%%><%%=EFSourceRoleName%%><%%-">%%>
            foreach (RelationshipProperty)
            {
                    // apparently the referential constraints can only handle primary keys, not unique indexes
                    if (ReferencedProperty.IsPrimaryKeyMember == true)
                    {
                        with (Property)
                        {
                    <%%-
              <PropertyRef Name="%%><%%=BLLPropertyName%%><%%-" />%%>
                     }
                 }
             }
            <%%-
            </Dependent>
          </ReferentialConstraint>
        </Association>%%>
}
<%%-
      </Schema>
    </edmx:ConceptualModels>%%>

以下是从生成的 edmx 文件中截取的片段,显示了概念模型中 `Customer` 的实体类型定义。

<EntityType Name="Customer">
      <Key>
        <PropertyRef Name="CustomerID" />
      </Key>
      <Property Name="CustomerID" Type="String" 
        Nullable="false" MaxLength="5" />
      <Property Name="CompanyName" Type="String" 
        Nullable="false" MaxLength="40" />
      <Property Name="ContactName" Type="String" MaxLength="30" />
      <Property Name="ContactTitle" Type="String" MaxLength="30" />
      <Property Name="Address" Type="String" MaxLength="60" />
      <Property Name="City" Type="String" MaxLength="15" />
      <Property Name="Region" Type="String" MaxLength="15" />
      <Property Name="PostalCode" Type="String" MaxLength="10" />
      <Property Name="Country" Type="String" MaxLength="15" />
      <Property Name="Phone" Type="String" MaxLength="24" />
      <Property Name="Fax" Type="String" MaxLength="24" />
      <NavigationProperty Name="OrderList" 
        Relationship="NorthwindModel.FK_Orders_Customers" 
        FromRole="Customer" ToRole="Order" />
      <NavigationProperty Name="CustomerCustomerDemoList" 
        Relationship="NorthwindModel.FK_CustomerCustomerDemo_Customers" 
        FromRole="Customer" ToRole="CustomerCustomerDemo" />
</EntityType>

OData 服务层

Mo+ 解决方案模型提供了生成利用实体框架的附加层的能力,而实体框架模型本身无法做到这一点。

例如,`EFDataServices` 模板会调用 `DataServicesClassCode` 代码模板来生成 `DataService`。该模板中的以下代码块显示了如何生成 `DataService` 类。

<%%-
    public class %%><%%=DSDataServiceName%%>
if (ProjectReferenceCount > 1)
{
    // get a project tagged as BLL
    foreach (Project where Tags.Contains("BLL") == true)
    {
        <%%- : DataService<%%><%%=DSModelClassName%%><%%->%%>
    }
}
else
{
    foreach (Project)
    {
        <%%- : DataService<%%><%%=DSModelClassName%%><%%->%%>
    }
}
<%%-
{
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {%%>
    foreach (Entity in Solution where Tags.Contains("DB") == true)
    {
        progress
    <%%-
        config.SetEntitySetAccessRule("%%><%%=BLLPluralEntityName%%><%%-", EntitySetRights.All);%%>
        progress
    }
        <%%-
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        config.DataServiceBehavior.AcceptProjectionRequests = true; 
    }

    protected override void HandleException(HandleExceptionArgs e) 
    {
        // add additional handling here if desired
        base.HandleException(e);
    } 
}%%> 

以下是从 `DataService` 类中截取的片段,该类提供了对 `Customer` 和其他实体操作的访问。

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using Northwind.EF;

namespace Northwind.DS
{
    public class EFDataServices : DataService<NorthwindEntities>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Categories", EntitySetRights.All);
            config.SetEntitySetAccessRule("CustomerCustomerDemos", EntitySetRights.All);
            config.SetEntitySetAccessRule("CustomerDemographics", EntitySetRights.All);
            config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
            config.SetEntitySetAccessRule("Employees", EntitySetRights.All);
            config.SetEntitySetAccessRule("EmployeeTerritories", EntitySetRights.All);
            config.SetEntitySetAccessRule("OrderDetails", EntitySetRights.All);
            config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
            config.SetEntitySetAccessRule("Products", EntitySetRights.All);
            config.SetEntitySetAccessRule("Regions", EntitySetRights.All);
            config.SetEntitySetAccessRule("Shippers", EntitySetRights.All);
            config.SetEntitySetAccessRule("Suppliers", EntitySetRights.All);
            config.SetEntitySetAccessRule("Territories", EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            config.DataServiceBehavior.AcceptProjectionRequests = true; 
        }

        protected override void HandleException(HandleExceptionArgs e) 
        {
            // add additional handling here if desired
            base.HandleException(e);
        } 
    }
} 

视图模型客户端层

视图模型客户端层消耗 OData 服务,并为用户界面和其他应用程序提供便捷的访问。

例如,`VMEFDS` 模板会调用 `VMEFDSViewModelClassCode` 代码模板为每个实体生成视图模型类。该模板中的以下代码块显示了视图模型类中用于消耗 OData 服务执行更新和重置操作的部分是如何生成的。

<%%-

namespace %%><%%=Project.Namespace%%><%%-.%%><%%=FeatureName%%><%%-
{
    public partial class %%><%%=VMBLLViewModelClassName%%><%%- : EditWorkspaceViewModel
    {

        #region "Command Processing"
        ///--------------------------------------------------------------------------------
        /// <summary>This method resets the data.</summary>
        ///--------------------------------------------------------------------------------
        protected override void OnReset()
        {
            try
            {
                // reset the underlying data for the %%><%%=BLLClassName%%><%%-
                Edit%%><%%=BLLClassName%%><%%- = null;
                %%><%%=BLLClassName%%><%%- = (from i in Context.%%><%%=BLLPluralEntityName%%>
                foreach (Property where IsPrimaryKeyMember == true)
                {
                    if (ItemIndex > 0)
                    {
                <%%-
                        && i.%%><%%=BLLPropertyName%%><%%- == 
                          %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%>
                    }
                    else
                    {
                <%%-
                        where i.%%><%%=BLLPropertyName%%><%%- == 
                          %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%>
                    }
                }
                <%%-
                        select i).FirstOrDefault<%%><%%=BLLClassName%%><%%->();
                %%><%%=BLLClassName%%><%%-_PropertyChanged(this, null);
            }
            catch
            {
                %%><%%=BLLClassName%%><%%- = new %%><%%=BLLClassName%%><%%-();%%>
                foreach (Property where IsPrimaryKeyMember == true && DataTypeCode == 26 /* Guid */)
                {
                <%%-
                %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%><%%- = Guid.NewGuid();%%>
                }
                <%%-
            }
            _isEdited = false;
        }

        ///--------------------------------------------------------------------------------
        /// <summary>This method updates the view model data and sends update command back
        /// to the solution builder.</summary>
        ///--------------------------------------------------------------------------------
        protected override void OnUpdate()
        {
            try
            {
                // get the edited information%%>
            foreach (Entity in BaseAndEntityEntities)
            {
                foreach (Property where IsAuditProperty == false)
                {
                <%%-
                %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%><%%- = 
                     Edit%%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%><%%-;%%>
                }
            }
            <%%-
%%>
            foreach (Entity in BaseAndEntityEntities)
            {
                if (ItemIndex > 0)
                {
                    <%%-
%%>
                }
            <%%-
                // perform the update of %%><%%=BLLClassName%%><%%-
                %%><%%=BLLClassName%%><%%- %%><%%=BLLClassName.CamelCase()%%><%%- = null;
                try
                {
                    %%><%%=BLLClassName.CamelCase()%%><%%- = 
                         (from i in Context.%%><%%=BLLPluralEntityName%%>
                foreach (Property where IsPrimaryKeyMember == true)
                {
                    if (ItemIndex > 0)
                    {
                <%%-
                            && i.%%><%%=BLLPropertyName%%><%%- == 
                                 %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%>
                    }
                    else
                    {
                <%%-
                            where i.%%><%%=BLLPropertyName%%><%%- == 
                                %%><%%=../BLLClassName%%><%%-.%%><%%=BLLPropertyName%%>
                    }
                }
                            <%%-
                            select i).FirstOrDefault<%%><%%=BLLClassName%%><%%->();
                }
                catch { }
                if (%%><%%=BLLClassName.CamelCase()%%><%%- == null)
                {
                    Context.AddTo%%><%%=BLLPluralEntityName%%><%%-(%%><%%=BLLClassName%%><%%-);%%>
                    foreach (Property where IsAuditProperty == true && DataTypeCode == 24 /* DateTime */)
                    {
                        with (AuditProperty from Solution.Find(PropertyName, PropertyName))
                        {
                            if (IsAddAuditProperty == true)
                            {
                    <%%-
                    %%><%%=../../BLLClassName%%><%%-.%%><%%=../BLLPropertyName%%><%%- = DateTime.Now;%%>
                            }
                        }
                    }
                    <%%-
                }
                else
                {
                    Context.UpdateObject(%%><%%=BLLClassName%%><%%-);
                }%%>
                foreach (Property where IsAuditProperty == true && DataTypeCode == 24 /* DateTime */)
                {
                    with (AuditProperty from Solution.Find(PropertyName, PropertyName))
                    {
                        if (IsUpdateAuditProperty == true)
                        {
                <%%-
                %%><%%=../../BLLClassName%%><%%-.%%><%%=../BLLPropertyName%%><%%- = DateTime.Now;%%>
                        }
                    }
                }
            }
                <%%-
                
                Context.SaveChanges();
                OnUpdated(this, null);
                ShowOutput("%%><%%=BLLClassName%%><%%- successfully updated.", 
                      "%%><%%=BLLClassName%%><%%- Update", true);
                _isEdited = false;
            }
            catch (System.Exception ex)
            {
                ShowException(ex, true);
            }
        }

        #endregion "Command Processing"
    }
}
%%>
%%> 

以下是从上面模板部分生成的 `Customer` 视图模型类中截取的片段。

namespace Northwind.VM.Domain
{
    public partial class CustomerViewModel : EditWorkspaceViewModel
    {


        #region "Command Processing"
        ///--------------------------------------------------------------------------------
        /// <summary>This method resets the data.</summary>
        ///--------------------------------------------------------------------------------
        protected override void OnReset()
        {
            try
            {
                // reset the underlying data for the Customer
                EditCustomer = null;
                Customer = (from i in Context.Customers
                        where i.CustomerID == Customer.CustomerID
                        select i).FirstOrDefault<Customer>();
                Customer_PropertyChanged(this, null);
            }
            catch
            {
                Customer = new Customer();
            }
            _isEdited = false;
        }

        ///--------------------------------------------------------------------------------
        /// <summary>This method updates the view model data and sends update command back
        /// to the solution builder.</summary>
        ///--------------------------------------------------------------------------------
        protected override void OnUpdate()
        {
            try
            {
                // get the edited information
                Customer.CustomerID = EditCustomer.CustomerID;
                Customer.CompanyName = EditCustomer.CompanyName;
                Customer.ContactName = EditCustomer.ContactName;
                Customer.ContactTitle = EditCustomer.ContactTitle;
                Customer.Address = EditCustomer.Address;
                Customer.City = EditCustomer.City;
                Customer.Region = EditCustomer.Region;
                Customer.PostalCode = EditCustomer.PostalCode;
                Customer.Country = EditCustomer.Country;
                Customer.Phone = EditCustomer.Phone;
                Customer.Fax = EditCustomer.Fax;

                // perform the update of Customer
                Customer customer = null;
                try
                {
                    customer = (from i in Context.Customers
                            where i.CustomerID == Customer.CustomerID
                            select i).FirstOrDefault<Customer>();
                }
                catch { }
                if (customer == null)
                {
                    Context.AddToCustomers(Customer);
                }
                else
                {
                    Context.UpdateObject(Customer);
                }
                
                Context.SaveChanges();
                OnUpdated(this, null);
                ShowOutput("Customer successfully updated.", 
                                "Customer Update", true);
                _isEdited = false;
            }
            catch (System.Exception ex)
            {
                ShowException(ex, true);
            }
        }


        #endregion "Command Processing"
    }
}

WPF 应用程序层

最后,WPF 管理用户界面利用了客户端视图模型层。

例如,`WPFUI` 模板会调用 `WPFUIDetailMarkupCode` 代码模板来生成用于查看和编辑 `Customer` 等实体的 XAML。该模板中的以下代码块显示了 XAML 是如何生成的。

<%%=USETABS false%%>
<%%:
<%%-
<UI:UIControl x:Class="%%><%%=Project.Namespace%%><%%-.UserControls.%%>
       <%%=FeatureName%%><%%-.%%><%%=WPFUIDetailClassName%%><%%-"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"%%>
                     
with (Project)
{
    if (ProjectReferenceCount > 1)
    {
        // get a project tagged as VM
        foreach (Project where Tags.Contains("VM") == true)
        {
                    <%%-
                     xmlns:%%><%%=../../FeatureName%%><%%-VM="clr-namespace:%%>
                       <%%=Namespace%%><%%-.%%><%%=../../FeatureName%%>
                       <%%-;assembly=%%><%%=Namespace%%><%%-"%%>
        }
    }
    else
    {
        foreach (Project)
        {
                    <%%-
                     xmlns:%%><%%=../../FeatureName%%><%%-VM="clr-namespace:%%>
                       <%%=Namespace%%><%%-.%%><%%=../../FeatureName%%>
                       <%%-;assembly=%%><%%=Namespace%%><%%-"%%>
        }
    }
}
                     <%%-
                     xmlns:UI="clr-namespace:%%><%%=Project.Namespace%%><%%-"
                     mc:Ignorable="d" 
                     d:DesignHeight="300" d:DesignWidth="300">
    <UI:UIControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../../Resources/Theme_G.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UI:UIControl.Resources>
    <ScrollViewer VerticalScrollBarVisibility="Auto" 
             HorizontalScrollBarVisibility="Auto" 
             Background="{StaticResource ControlBackgroundBrush}">
        <Grid MaxWidth="{Binding ActualWidth, RelativeSource=
                     {RelativeSource FindAncestor, AncestorType=ScrollViewer}}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>%%>
foreach (Entity in EntityAndBaseEntities)
{
    foreach (Property where IsUIEditableProperty == true)
    {
                <%%-
                <RowDefinition Height="Auto"></RowDefinition>%%>
    }
}
                <%%-
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="10"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="10"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" 
              Style="{DynamicResource LabelHeader}" 
              Content="%%><%%=BLLClassName%%><%%- Details" />%%>
log("WPFUIDetailMarkupCode", "RowIndex", 0)
foreach (Entity in EntityAndBaseEntities)
{
    foreach (Property where IsUIEditableProperty == true)
    {
        log("WPFUIDetailMarkupCode", "RowIndex", 
               LogValue("WPFUIDetailMarkupCode", "RowIndex") + 1)
        if (DataTypeCode != 12 /* Boolean */)
        {
            <%%-
            <Label Grid.Row="%%><%%=LogValue("WPFUIDetailMarkupCode", 
              "RowIndex")%%><%%-" Grid.Column="1" 
              Style="{StaticResource LabelInput}" 
              Content="%%><%%=BLLPropertyName%%><%%-:" />%%>
        }
    }
}
log("WPFUIDetailMarkupCode", "RowIndex", 0)
foreach (Entity in EntityAndBaseEntities)
{
    foreach (Property where IsUIEditableProperty == true)
    {
        log("WPFUIDetailMarkupCode", "RowIndex", 
               LogValue("WPFUIDetailMarkupCode", "RowIndex") + 1)
        if (DataTypeCode != 12 /* Boolean */)
        {
            <%%-
            <TextBox Grid.Row="%%><%%=LogValue("WPFUIDetailMarkupCode", 
              "RowIndex")%%><%%-" Grid.Column="2" 
              Text="{Binding %%><%%=BLLPropertyName%%><%%-, Mode=TwoWay, 
                    ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>%%>
        }
        else
        {
            <%%-
            <CheckBox Grid.Row="%%><%%=LogValue("WPFUIDetailMarkupCode", 
              "RowIndex")%%><%%-" Grid.Column="2" 
              Style="{StaticResource BasicCheckBox}" 
              Content="%%><%%=BLLPropertyName%%><%%-" 
              IsChecked="{Binding %%><%%=BLLPropertyName%%><%%-}" />%%>
        }
    }
}
log("WPFUIDetailMarkupCode", "RowIndex", 
    LogValue("WPFUIDetailMarkupCode", "RowIndex") + 1)
            <%%-
            <StackPanel Orientation="Horizontal" 
              Grid.Row="%%><%%=LogValue("WPFUIDetailMarkupCode", 
              "RowIndex")%%><%%-" Grid.Column="2" Margin="5">
                <Button Command="{Binding UpdateCommand}" 
                  Content="{Binding UpdateButtonLabel}"></Button>
                <Button Command="{Binding ResetCommand}" 
                  Content="{Binding ResetButtonLabel}"></Button>
                <Button Command="{Binding CloseConfirmCommand}" 
                  Content="{Binding CloseButtonLabel}"></Button>
            </StackPanel>
        </Grid>
    </ScrollViewer>
</UI:UIControl>%%>
%%> 

以下是 `Customer` 详细信息控件的 XAML。

<UI:UIControl x:Class="Northwind.UI.UserControls.Domain.CustomerDetail"
                     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                     xmlns:DomainVM="clr-namespace:Northwind.VM.Domain;assembly=Northwind.VM"
                     xmlns:UI="clr-namespace:Northwind.UI"
                     mc:Ignorable="d" 
                     d:DesignHeight="300" d:DesignWidth="300">
    <UI:UIControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../../Resources/Theme_G.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UI:UIControl.Resources>
    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" 
                Background="{StaticResource ControlBackgroundBrush}">
        <Grid MaxWidth="{Binding ActualWidth, RelativeSource=
                       {RelativeSource FindAncestor, AncestorType=ScrollViewer}}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="10"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="10"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" 
               Style="{DynamicResource LabelHeader}" Content="Customer Details" />
            <Label Grid.Row="1" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="CustomerID:" />
            <Label Grid.Row="2" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="CompanyName:" />
            <Label Grid.Row="3" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="ContactName:" />
            <Label Grid.Row="4" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="ContactTitle:" />
            <Label Grid.Row="5" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="Address:" />
            <Label Grid.Row="6" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="City:" />
            <Label Grid.Row="7" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="Region:" />
            <Label Grid.Row="8" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="PostalCode:" />
            <Label Grid.Row="9" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="Country:" />
            <Label Grid.Row="10" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="Phone:" />
            <Label Grid.Row="11" Grid.Column="1" 
               Style="{StaticResource LabelInput}" Content="Fax:" />
            <TextBox Grid.Row="1" Grid.Column="2" 
              Text="{Binding CustomerID, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="2" Grid.Column="2" 
              Text="{Binding CompanyName, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="3" Grid.Column="2" 
              Text="{Binding ContactName, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="4" Grid.Column="2" 
              Text="{Binding ContactTitle, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="5" Grid.Column="2" 
              Text="{Binding Address, Mode=TwoWay, ValidatesOnDataErrors=True, 
                     UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="6" Grid.Column="2" 
              Text="{Binding City, Mode=TwoWay, ValidatesOnDataErrors=True, 
                     UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="7" Grid.Column="2" 
              Text="{Binding Region, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="8" Grid.Column="2" 
              Text="{Binding PostalCode, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="9" Grid.Column="2" 
              Text="{Binding Country, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="10" Grid.Column="2" 
              Text="{Binding Phone, Mode=TwoWay, ValidatesOnDataErrors=True, 
                     UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <TextBox Grid.Row="11" Grid.Column="2" 
              Text="{Binding Fax, Mode=TwoWay, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=PropertyChanged}" 
              Validation.ErrorTemplate="{x:Null}"/>
            <StackPanel Orientation="Horizontal" Grid.Row="12" 
                        Grid.Column="2" Margin="5">
                <Button Command="{Binding UpdateCommand}" 
                  Content="{Binding UpdateButtonLabel}"></Button>
                <Button Command="{Binding ResetCommand}" 
                  Content="{Binding ResetButtonLabel}"></Button>
                <Button Command="{Binding CloseConfirmCommand}" 
                  Content="{Binding CloseButtonLabel}"></Button>
            </StackPanel>
        </Grid>
    </ScrollViewer>
</UI:UIControl> 

结论

希望本文能让您对 Mo+ 的代码生成方法有所了解。如果文章中有任何需要澄清之处,请告知。

Mo+ 模型驱动模板可以轻松用作构建块,将模型数据转化为复杂的多层应用程序。您有能力生成其他模型,例如实体框架模型,并根据您的规则和最佳实践来构建和维护这些模型。

下载内容

Northwind 解决方案下载是本文概述的实体框架解决方案。Northwind EF Code First 解决方案下载是类似的解决方案,但它利用了实体框架 Code First 方法。对于这两个解决方案,您都需要将连接字符串连接到您的 Northwind 数据库。

请访问 此处的 Mo+ 文章,获取示例包,以便您自己生成这两个解决方案。

成为会员!

Mo+ 社区通过网站 https://modelorientedplus.com 获得额外的支持,并为 Mo+ 的发展做出贡献。成为会员可以为 Mo+ 用户带来额外的好处,例如额外的论坛支持、会员贡献的工具,以及对 Mo+ 方向进行投票和/或贡献的能力。此外,我们将每月为会员举办比赛,您可以通过开发使用 Mo+ 的解决方案来赢得奖金。

如果您对高效的模型驱动软件开发有一丝兴趣,请注册成为会员。它是免费的,您不会收到垃圾邮件!

历史

© . All rights reserved.