Language And Object Relational Mapping Demo

This demo, based on SqlServer, is meant to showcase the strongly typed excellence and range of the WYSE framework, along with its groundbreaking (Hierarchical) Object Relation Mapping capabilities. The idea is to be able to code without having to make code quality damaging compromises.

...and since code speaks louder than words demo code (stack trace) and database results are shown in a developer-tools-like panel as you interact with the server.

Below you can find, grouped firstly by SQL statement type and later by object relational mapping feature, a list of links. These links are server calls in which increasingly advanced WYSE features of language and ORM are used.

Plain Text Expression Building

Be sure to check out the Report Generator demo. That playable demo explores what is possible in combination with self-constructed plain text where clauses. So it is functionally similar to GraphQL.

Working With State (BETA)

The 'Generic Mappings With SelectBuilder' examples listed above showcase how objects can be fully instantiated hierarchically. But that is only the 'reading' part. The 'writing' part is where State comes into play. With this, given an object with properties that have properties and so forth, a script will be generated to sync the state of the database with with that of the object. This script will contain all the necessary inserts, updates and/or deletes. The deletes are important when a property is a list of objects, so with 1:n relationships.

The small window you are using greatly diminishes your demo experience. Please consider viewing using a larger window.

Behind The Scenes Explanation

This is where you get to see what's happening behind the scenes when you interact with the site on the left side.

The view consists of three (collapsible) parts...

1. Full Code, Queries, And Results

The server handles the web requests that are the result of you interacting with the site.
In doing so, the demo backend code is executed which in turn uses WYSE.

The relevant/notable methods taking part in that process are shown in the first vertical tab.
Basically you are seeing a (partial) stack trace.
It is tree structure of which the nodes are collapsible/expandable for your browsing convenience.

The leaves of the STACK TRACE TREE are where interaction with the database takes place.
That is generally where you will find the generated SQL statements and the JSON of the converted database results.

2. Queries And Results Only

A filtered list of entries where only the database queries and results are shown.

3. API Results

If applicable: this shows the JSON of the call to the server as if it had been an API call.

  • Trace Entries: 2
      ...Framework.Controllers.HomeController.GetCustomersWithAddressesGenerically:
      [HttpGet]
      public async Task<ViewResult> GetCustomersWithAddressesGenerically(
          [FromQuery] PagingSettings pagingSettings)
      {
          var traceTree = _traceTreesRepository.Record();
          traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
      
          var result = await _customerService.GetWithAddressesGenericallyResult(
              pagingSettings,
              traceTree);
      
          return CreateView(traceTree, result);
      }
    • Trace Entries: 4
        ...Framework.Services.CustomerService.GetWithAddressesGenericallyResult:
        public async Task<PagedResult<Customer>> GetWithAddressesGenericallyResult(
            PagingSettings pagingSettings,
            TraceTree parentTraceTree)
        {
            var traceTree = parentTraceTree.AddChild();
            traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
        
            // Inherit from WYSE Select...
            // CustomerSubselect supports IGenericMappingsSupplier
            // The Select (its top level joins) plus generic mappings determine specific mappings
            var customerSubselect = new CustomerSubselect(traceTree, pagingSettings);
            var homeAddressSubselect = new AddressSubselect();
            var billingAddressSubselect = new AddressSubselect();
        
            Select select = new /*WYSE*/Select()
                .Everything()  // Effectively an '*', but explicit
                .From(customerSubselect)
                .InnerJoin(homeAddressSubselect, customerSubselect.HomeAddressId == homeAddressSubselect.Id)
                .LeftJoin(billingAddressSubselect, customerSubselect.BillingAddressId == billingAddressSubselect.Id);
        
            return await GetItemTreesAsync(
                select,
                pagingSettings,
                customerSubselect.Convert,
                traceTree);
        }
      • Trace Entries: 1
          ...Framework.DataSpace.Queries.ItemSubselectBase ctor:
          /*
           * ItemSubselectBase is the Framework Demo base class for all subselects used in
           * repositories and other dynamic query building.
           * This class inherits from WYSE's convertable subselect that enables query result row 
           * conversion (extending DbDataReader) and (Hierarchical) Object Relational Mapping.
           * Usage examples (different overloads):
           * - var select = new AddressSelect()  // Gets all addresses
           * - var select = new AddressSelect(id: new Guid("11111111-1111-1111-1111-111111111111"))  // Get a single address by id
           * - var select = new AddressSelect(isDeleted: true)  // Only gets the deleted addresses
           * - var select = new AddressSelect(new PagingSettings { PageNumber = 3, PageSize = 20 })
           * So a Select with WYSE can be used to encapsulate SQL logic,
           * making it kind of like a coded version of a SqlServer function or view,
           * but much more flexible!
           */
          protected ItemSubselectBase(
              ItemTableBase<TItem> sourceTable,
              TraceTree parentTraceTree,
              PagingSettings? pagingSettings,  /* If not null then paging is added to the query */
              Guid? id,  /* If not null then a select by id is done */
              bool? isDeleted)  /* If 'null' then both deleted and not deleted */
          {
              var traceTree = parentTraceTree?.AddChild();
              traceTree?.RecordCodeConstructorExecution(typeof(ItemSubselectBase<TSelect, TItem>));
          
              _sourceListConvert = sourceTable.Convert;
          
              // Funneling is a concept used in WYSE to expose the columns of a (nested)
              // subselect one level higher in a query, so it can be used in a From or in
              // Where/Join expressions for example.
              Id = /*WYSE*/Funnel(sourceTable.Id);
              Created = /*WYSE*/Funnel(sourceTable.Created);
              LastModified = /*WYSE*/Funnel(sourceTable.LastModified);
              IsDeleted = /*WYSE*/Funnel(sourceTable.IsDeleted);
          
              var columnExpressions = new List</*WYSE*/IExpression>(
                  sourceTable./*WYSE*/ListColumns());
          
              if (pagingSettings is not null)
              {
                  // Add SqlServer's 'COUNT(Id) OVER()' to selected columns.
                  // The modifying of the Where clause for row selection is done in
                  // the query backbone. Check the traces below.
                  sourceTable.CountUnpaged = new /*WYSE*/Count(sourceTable.Id).Over();
                  CountUnpaged = /*WYSE*/Funnel(sourceTable.CountUnpaged);
          
                  columnExpressions.Add(sourceTable.CountUnpaged);
              }
          
              // This makes the statement: SELECT [columnExpressions] FROM [sourceList]
              /*WYSE*/From from = this./*WYSE*/Columns(columnExpressions).From(sourceTable);
          
              // Determine the applicable where clause sub expressions (based on SQL parameters)
              var whereExpressions = new List</*WYSE*/BooleanExpression>();
          
              if (id.HasValue)
              {
                  whereExpressions.Add(
                      sourceTable.Id == new /*WYSE*/UniqueIdentifierParam("Id", id));
              }
              if (isDeleted.HasValue)
              {
                  whereExpressions.Add(
                      sourceTable.IsDeleted == new /*WYSE*/BitParam("IsDeleted", isDeleted));
              }
          
              if (whereExpressions.Count > 0)
              {
                  // Append a Where clause with a condition that is all the
                  // sub expressions 'AND'-ed together
                  from.Where(whereExpressions./*WYSE*/And());
              }
          }
      • Trace Entries: 3
          ...Framework.Services.ServiceBase.GetItemTreesAsync:
          protected async Task<PagedResult<TItem>> GetItemTreesAsync<TItem>(
              Select select,
              PagingSettings pagingSettings,
              Converter<DbDataReader, TItem> converter,
              TraceTree parentTraceTree)
              where TItem : ItemBase
          {
              var traceTree = parentTraceTree.AddChild();
              traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
          
              // In Wyse, mappings facilitate Hierarchical Object Relational Mapping.
              // The information to do is reflected/extracted from the Select at runtime.
              // This way you have complete control of how your objects are built and you always
              // have the desired Select/mappings combination.
              // No more no less, in the right place, always. Best efficiency possible.
              // NOTE: Select reflection is based on the implementation of an interface of both
              // the list involved in the join as the (list of) POCO domain object(s).
              // PS: Ad hoc (custom) mapping can be used as well, and in conjunction with reflection.
              var mappings = /*WYSE*/MappingsFactory.GetMappings(select);
          
              // A scriptable can be anything in the WYSE language that can be part of a script:
              // - Executables; like Selects, Inserts, Updates, Deletes, Merges (SqlServer only)
              // - Variable and Temp table declarations
              // - If/Elses
              // ...and more.
              var scriptables = new List</*WYSE*/IScriptable>();
          
              _queryBackbone.Using(pagingSettings, traceTree).Modify(select, scriptables);
          
              var pageItems = await _sqlExecuter.GetScriptItemTreesAsync(
                  // Create new script combining scriptables with select
                  new Script(scriptables) + select,
                  select,
                  converter,  /* of the primary/root table, used in the From of the Select */
                  mappings,
                  parentTraceTree);
          
              return new PagedResult<TItem>(pagingSettings, pageItems);
          }
        • Trace Entries: 1
            ...Framework.DataSpace.Queries.Modification.QueryBackbone.Modify:
            public override void Modify(
                /*WYSE*/Select select,
                List</*WYSE*/IScriptable> preparationScriptables)
            {
                var traceTree = _parentTraceTree.AddChild();
                traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
            
                // You can build a pipleline of modifiers to modify an executable (here a Select),
                // and add scriptables (like table variables) used in the modified Select.
                // These modifiers let you cut/paste strongly typed SQL language snippets.
                // In the case of this Framework Demo there are 3 custom modifiers:
                // - Division security decorator
                // - Paging
                // - Identifiers resetter
                // ...more on those below.
                base.Modify(select, preparationScriptables);
            }
        • Trace Entries: 4
            ...Framework.DataSpace.Queries.Modification.DivisionSecurityDecorator.Modify:
            public void Modify(/*WYSE*/Select select, List</*WYSE*/IScriptable> scriptables)
            {
                var traceTree = _parentTraceTree.AddChild();
                traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
            
                if (!select.Uses<IDivisionReferringTable>())
                {
                    return;
                }
            
                // Put all the Ids of the divisions the current user has rights to in a 
                // TableVariable (Inherits from WYSE SqlServer TableVariable).
                var userDivisionIdsTableVariable = new UserDivisionIdsTableVariable();
            
                scriptables.Add(new /*WYSE*/Declare(userDivisionIdsTableVariable));
            
                var fillTableVariable = new UserDivisionIdsTableVariable.Fill(
                    userDivisionIdsTableVariable,
                    _sessionContext.CurrentUser.DivisionId,
                    traceTree);
            
                scriptables.Add(fillTableVariable);
            
                // This is a WYSE ISnippetReplacementFactory
                var securityReplacement = new SecurityReplacement(
                    userDivisionIdsTableVariable,
                    traceTree);
            
                // SQL Statements can be remade by chaining multiple ISnippetReplacementFactory instances.
                // Here just one is needed.
                _ = /*WYSE*/StatementRebuilder.Rebuild(
                    select,
                    /* rebuild the original Select, instead of a clone of it that is returned */
                    rebuildOnClone: false,
                    [securityReplacement]);
            }
          • Trace Entries: 2
              ...Framework.DataSpace.Queries.Systems.UserDivisionIdsTableVariable+Fill ctor:
              // The Fill class inherits from WYSE InsertInto
              public Fill(
                  UserDivisionIdsTableVariable tableVariable,
                  Guid userDivisionId,
                  TraceTree parentTraceTree)
                  : base(tableVariable)
              {
                  var traceTree = parentTraceTree.AddChild();
                  traceTree.RecordCodeConstructorExecution(typeof(Fill));
              
                  var getDescendents = new GetDescendentsTableFunction(userDivisionId, traceTree);
              
                  Select getDescendentsSelect = new /*WYSE*/Select()
                      .Columns(getDescendents.DivisionId)
                      .From(getDescendents);
              
                  // Insert into tableVariable the Select result of the table function.
                  this./*WYSE*/Columns(tableVariable)
                      .Select(getDescendentsSelect);
              }
            • Trace Entries: 1
                ...Framework.DataSpace.Entities.Relational.Systems.GetDescendantsTableFunction ctor:
                // Returns a table with a division id and the ids of all its descendents.
                public GetDescendantsTableFunction(
                    Guid divisionId,
                    TraceTree parentTraceTree)
                    : base()
                {
                    var traceTree = parentTraceTree.AddChild();
                    traceTree.RecordCodeConstructorExecution(GetType());
                
                    // When just using/calling the function setting the parameter value is enough.
                    DivisionIdParam.SetValue(divisionId);
                
                    var parentTable = new DivisionHierarchyTable().WithAlias("parent");
                    var childTable = new DivisionHierarchyTable().WithAlias("child");
                    var inTable = new DivisionHierarchyTable().WithAlias("i");
                
                    // This Select is NOT necessary when merely calling the function.
                    // (only needed when WYSE deploys the database).
                    // The Select is added here just to show what is under the GetDescendents-hood.
                    Select = new Select()
                        .Distinct()
                        .Columns(childTable.DivisionId.WithAlias("DivisionId"))
                        .From(parentTable)
                        // DivisionNodeId is of SqlServer type 'HierarchyId'
                        // IsDescendantOf is a built-in SqlServer function that WYSE supports
                        .InnerJoin(
                            childTable,
                            childTable.DivisionNodeId
                                .IsDescendantOf(parentTable.DivisionNodeId) == true)
                        .Where(parentTable.DivisionId.In(
                            new Select()
                                .Columns(inTable.DivisionId.WithAlias("DivisionId"))
                                .From(inTable)
                                .Where(inTable.DivisionId == DivisionIdParam)));
                }
          • Trace Entries: 1
              ...Framework.DataSpace.Queries.Modification.DivisionSecurityDecorator+SecurityReplacement.MustReplace:
              // In WYSE you can control whether or not replacement is necessary
              // for a SQL statement snippet.
              public bool MustReplace(Snippet snippet)
              {
                  if (!_mustReplaceTraceHasBeenRecorded)
                  {
                      var traceTree = _parentTraceTree.AddChild();
                      traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
                      _mustReplaceTraceHasBeenRecorded = true;
                  }
              
                  return
                      snippet is /*WYSE*/From from &&
                          from.List is IDivisionReferringTable
                      ||
                      snippet is /*WYSE*/Join join &&
                          join.TargetList is IDivisionReferringTable;
              }
          • Trace Entries: 2
              ...Framework.DataSpace.Queries.Modification.DivisionSecurityDecorator+SecurityReplacement.GetReplacements:
              // In WYSE replacing can be used to remove, change, or add as you see fit.
              public IReadOnlyList</*WYSE*/Snippet> GetReplacements(
                  /*WYSE*/Snippet snippet)
              {
                  var traceTree = _parentTraceTree.AddChild();
                  traceTree.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
              
                  // In this case we always want to keep the original SQL snippet.
                  var replacements = new List</*WYSE*/Snippet> { snippet };
              
                  if (snippet is /*WYSE*/From from &&
                      from.List is IDivisionReferringTable fromTable)
                  {
                      replacements.Add(CreateDivisionSecurityJoin(fromTable, traceTree));
                  }
                  else if (snippet is /*WYSE*/Join join &&
                      join.TargetList is IDivisionReferringTable joinTable)
                  {
                      replacements.Add(CreateDivisionSecurityJoin(joinTable, traceTree));
                  }
              
                  return replacements;
              }
            • Trace Entries: 0
      • Trace Entries: 1
          ...Framework.DataSpace.Queries.Customers.CustomerSubselect.GetGenericMappings:
          // Generic mappings are WYSE's way of defining mappings separate from a
          // specific Select, in order to facilitate reuse.
          public IReadOnlyList</*WYSE*/GenericMapping> GetGenericMappings()
          {
              var traceTree = _parentTraceTree?.AddChild();
              traceTree?.RecordCodeMethodExecution(MethodBase.GetCurrentMethod());
          
              if (_genericMappings is null)
              {
                  // Factory for creating generic mappings starting from a
                  // 'CustomerSubselect' source list, having a domain object
                  // of type 'Customer'
                  var factory = new /*WYSE*/GenericMappingsFactory<CustomerSubselect, Customer>();
          
                  _genericMappings = new /*WYSE*/GenericMappingList(
                      // Target list is a 'AddressSubselect' and maps to a type 'Address'
                      factory.CreateMapping<AddressSubselect, Address>(
                          (s, a) => s.HomeAddressId == a.Id,
                          c => c.HomeAddress),
                      factory.CreateMapping<AddressSubselect, Address>(
                          (s, a) => s.BillingAddressId == a.Id,
                          c => c.BillingAddress));
              }
              
              return _genericMappings;
          }