Click here to Skip to main content
Click here to Skip to main content
Go to top

Mo+ - Supersizing Entity Framework to Generate a Working Multi-tier Application

, 18 Jul 2014
Rate this:
Please Sign up or sign in to vote.
Using Mo+ to generate a working application, which includes an Entity Framework model, OData services, view model client, and WPF application.

Introduction

We believe in empowering software developers to focus more on ideas and less on the mundane details in developing software. We believe that evolving a template based code generation approach into a model driven and model oriented approach is the best way to manage high quality generated code that matches your best practices, and integrates seamlessly with your custom code.

Object oriented programmers know the benefits of object oriented languages over procedural languages in creating large and complex software, benefits such as encapsulation, reusability, modularity, clarity, etc. Why shouldn't you have these same benefits from a code generation perspective? You do with Mo+! The Mo+ approach to code generation is the only one that is template based, model driven, truly model oriented, and focuses on code maintenance as well as initial code generation.

In this article, we will generate a working multi-tier application with the Mo+, utilizing Entity Framework. We will use the Northwind SQL Server database, and with Mo+ templates, generate the Entity Framework layer, an OData services layer, a client view model layer, and a WPF administrative UI application. A running application will be generated without any custom code.

Background

The Mo+ model oriented programming language and the Mo+ Solution Builder IDE for model oriented development was introduced and explained in this CodeProject article. Here, you can get the Mo+ installer and sample packs for generating the multi-tier application in this article.

The Mo+ open source technology is available at moplus.codeplex.com, which includes the latest installs, source code, sample packs (template libraries), and other materials. Video tutorials are also available at this site. The Mo+ Solution Builder also contains extensive on board help.

If you are following this article while using Mo+ Solution Builder and need to know how to load a solution model from a database, watch this tutorial on Loading a Model from SQL Server. You can also watch Create a Multi Tier Application, which creates the same application as this article, and Update a Multi Tier Application, which runs through some update scenarios with this application.

Creating the Application

Entity Framework is an effective ORM that enables .NET developers to work with relational data using domain-specific objects. It eliminates the need for most of the data-access code that developers usually need to write. But, since the Entity Framework model is tied to the data access layer project, you are on your own in building other layers to utilize this framework.

With Mo+, you can not only generate and maintain the Entity Framework model, you can generate and maintain a whole solution that utilizes Entity Framework. Using the Northwind SQL Server database, we will create a working application which will include the Entity Framework data access layer, an OData services layer, a client view model layer, and a WPF administrative UI application.

To create this application, we start by creating a Mo+ solution model. See the Background section above for details on how to use Mo+ Solution Builder to create a solution and utilize templates from the sample packs. Our solution model needs to include the following information:

  • Basic solution information, which includes the SolutionFile template to generate your overall solution.
  • A DatabaseSource, which provides connection information to the Northwind database. The MDLSqlModel SQL Server spec template is chosen to load the model from the database. Here, you have complete control over how your model is loaded with Entity, Property, and Relationship information, etc., and how those elements are to be named.
  • A Project, which specifies that an Entity Framework data access layer is to be created. The EntityFramework code template is chosen to create and maintain this layer.
  • A Project, which specifies that an OData services layer is to be created. The EFDataServices code template is chosen to create and maintain this layer. This layer utilizes the Entity Framework layer.
  • A Project, which specifies that a client view model layer is to be created. The VMEFDS code template is chosen to create and maintain this layer. This layer utilizes the OData services layer.
  • A Project, which specifies that a WPF UI layer is to be created. The WPFUI code template is chosen to create and maintain this layer. This layer utilizes the client view model layer.

Following is the solution model file (also attached) with all of the information in its raw format. You can copy this solution and change the template locations and database information.

<?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> 

The following image shows the solution model information in the tree view within Mo+ Solution Builder:

Once this solution is set up, you can use Mo+ Solution Builder to generate the application, and to automatically update it when changes to the database schema are performed. The following image shows some Entity and Property information in the solution model, as loaded from the Northwind database:

The following image shows a view of the running generated WPF application:

Looking at Template and Code Samples

We seriously breezed through the details as to how this working application was created. We will take a look at a sample of template code and output solution code relating to the Customer object at each layer. If you want to look deeper, load the templates in Mo+ Solution Builder. You can also run the templates in the debugger and inspect what is happening along the way.

Entity Framework Layer

The Mo+ solution model has all of the information necessary for generating and maintaining Entity Framework models. It's merely a matter of translating information from one model to another.

For example, the EntityFramework code template calls the EFModelMarkupCode template to create the Entity Framework model markup (edmx) file. The following block of code in that template shows how the Entity Framework conceptual model is created.

<%%-<!-- 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>%%>

The following is an excerpt from the generated edmx file, and shows the entity type definition for Customer in the conceptual model:

<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 Services Layer

The Mo+ solution model provides the ability to generate additional layers utilizing Entity Framework that the Entity Framework model alone cannot do.

For example, the EFDataServices template calls the DataServicesClassCode code template to generate the DataService. The following block of code in that template shows how the DataService class is generated:

<%%-
    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);
    } 
}%%> 

Following is an excerpt from the DataService class, which provides access to Customer and other entity operations:

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);
        } 
    }
} 

View Model Client Layer

The view model client layer consumes the OData service, and enables ready access for user interface and other applications.

For example, the VMEFDS template calls the VMEFDSViewModelClassCode code template to generate the view model class for each entity. The following block of code in that template shows how the portion of the view model class that consumes the OData service to perform update and reset operations is generated:

<%%-

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"
    }
}
%%>
%%> 

Following is an excerpt from the Customer view model class that is generated from the portion of the template above:

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 Application Layer

Finally, the WPF administrative UI utilizes the view model client layer.

For example, the WPFUI template calls the WPFUIDetailMarkupCode code template to generate the XAML for viewing and editing entities such as Customer. The following block of code in that template shows how the XAML is generated:

<%%=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>%%>
%%> 

Following is the XAML for the Customer detail control:

<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> 

In Conclusion

Hopefully, this article has given you an idea of the power of the Mo+ approach to code generation. Please let me know if anything in the article requires clarification.

Mo+ model oriented templates can be easily used as building blocks to transform model data into complex and multi-layered applications. You have the ability to generate other models such as Entity Framework models, and build and maintain those models in accordance to your rules and best practices.

Additional EF Code First Templates

The original article utilized Mo+ templates using the Entity Framework code first approach. The additional EF Code First template pack allows you to create a project using EF Code First. Please review the associated readme file for instructions.

Become a Member!

The Mo+ community gets additional support, and contributes to the evolution of Mo+ via the web site at https://modelorientedplus.com. Being a member gives Mo+ users additional benefits such as additional forum support, member contributed tools, and the ability to vote on and/or contribute to the direction of Mo+. Also, we will be running monthly contests for members, where you can win $ for developing solutions using Mo+.

If you are the least bit interested in efficient model driven software development, please sign up as a member. It's free, and you won't get spam email!

History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dave Clemmer
Software Developer Intelligent Coding Solutions, LLC
United States United States
I enjoy coding like an excellent beer. My particular passion and experience lies in the realm of modeling and code generation. In the late 80s and early 90s, I was involved in early modeling and code generation tools that reached the marketplace, including a tool that modeled FORTRAN programs and generated FORTRAN for parallel supercomputer architectures, and a tool that managed Shlaer-Mellor models and generated C++ code. Over the years, I have applied Shlaer-Mellor, UML, and custom modeling and various code generation techniques to greatly benefit the development of enterprise applications.
 
My current passion and endeavor is to foster the evolution of the Mo+ model oriented programming language and related model driven development tools, with as much community input as possible to achieve higher levels in the ability to create and maintain code. The open source site is at moplus.codeplex.com, and the Mo+ membership site is at modelorientedplus.com.
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 18 Jul 2014
Article Copyright 2013 by Dave Clemmer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid