Skip to content

Built In Aspects

Simon Mourier edited this page Feb 19, 2020 · 1 revision

This section details and illustrates how to use out-of-the-box (or “Official”) shipped aspects.

To add an aspect, use the Aspects folder node in the Visual Studio project. By default, the Browse dialog box will point you to the Official aspects directory which should be something like:

%localappdata%\Microsoft\VisualStudio\16.0_26dd5244\Extensions\SoftFluent S.A.S\SoftFluent CodeModeler\1.3.0.0\Patterns

Note: Adapt 16.0_26dd5244 and/or 1.3.0.0 to your context.

The dialog box will automatically parse (or compile) the path and show the metadata it has extracted from it, as in the following picture:

Built-In Aspects - Picture 329

The aspect description is a text, and the Aspect Descriptors are attributes specific to the selected aspect, that can be used in the Visual Studio property grid when the aspects is added to the project.

Once an aspect has been added to the project, you can configure its attributes (from descriptors definition) using the Visual Studio property grid, “Aspects and Producers Properties” tab (1), grouped by Descriptor Category (2):

Built-In Aspects - Picture 330

Note: All official SoftFluent aspects are developed using Xml parts, so you can easily see how they are programmed and modify their behavior if needed.

The Localization Aspect

The localization aspect enables database localization (a.k.a. dynamic localization as opposed to using resources in .resx files) support.

To enable the aspect, you must add the “Softfluent.Localization.xml” file.

For example, for this simple model with a Ministry entity:

The Localization Aspect - Picture 332

You can enable it for a given property using the “Aspects and Producers Properties” tab (once the aspect has been added to the project):

The Localization Aspect - Picture 331

When properties have been defined as localizable, once the project is built, there is one extra table per entity that contains localizable properties (with proper foreign keys in place as well):

The Localization Aspect - Picture 333

And all load stored procedures have also been modified to join with the localized table:

CREATE PROCEDURE [Commerce].[Ministry_LoadAll]
(
 @Lcid [int] = 1033,
 @_orderBy0 [nvarchar] (64) = NULL,
 @_orderByDirection0 [bit] = 0
)
AS
SET NOCOUNT ON
IF(@Lcid IS NULL)
BEGIN
    SELECT DISTINCT [Commerce].[vMinistryLocalized].[Ministry_Id], [Commerce].[vMinistryLocalized].[Ministry_Label], [Commerce].[vMinistryLocalized].[Lcid], [Commerce].[vMinistryLocalized].[_rowVersion], [Commerce].[vMinistryLocalized].[_trackCreationTime], [Commerce].[vMinistryLocalized].[_trackLastWriteTime], [Commerce].[vMinistryLocalized].[_trackCreationUser], [Commerce].[vMinistryLocalized].[_trackLastWriteUser] 
        FROM [Commerce].[vMinistryLocalized] 
        WHERE ([Commerce].[vMinistryLocalized].[Lcid] = 65536)
        ORDER BY [Commerce].[vMinistryLocalized].[Lcid] ASC
END
ELSE
BEGIN
    SELECT DISTINCT [Commerce].[vMinistryLocalized].[Ministry_Id], [Commerce].[vMinistryLocalized].[Ministry_Label], [Commerce].[vMinistryLocalized].[Lcid], [Commerce].[vMinistryLocalized].[_rowVersion], [Commerce].[vMinistryLocalized].[_trackCreationTime], [Commerce].[vMinistryLocalized].[_trackLastWriteTime], [Commerce].[vMinistryLocalized].[_trackCreationUser], [Commerce].[vMinistryLocalized].[_trackLastWriteUser] 
        FROM [Commerce].[vMinistryLocalized] 
        WHERE ((@Lcid = [Commerce].[vMinistryLocalized].[Lcid]) OR ([Commerce].[vMinistryLocalized].[Lcid] = 65536))
        ORDER BY [Commerce].[vMinistryLocalized].[Lcid] ASC
END

Note: To understand the details we suggest you look at the aspect’s source code

Instances Localization Support

Once at least a property has been defined as localizable, the Instance Editor has an extra “Localized Values” tab that allows you to define localizable values per localizable property:

Instances Localization Support - Picture 334

This tab allows you to edit the culture-specific property value. You can add any culture you want.

Instances Localization Support - Picture 335

This is the content of the database once the project has been built:

Instances Localization Support - Picture 336

And the following C# code:

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
foreach (var ministry in MinistryCollection.LoadAll())
{
    Console.WriteLine(ministry.Label);
}
 
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("fr-FR");
foreach (var ministry in MinistryCollection.LoadAll())
{
    Console.WriteLine(ministry.Label);
}

Will output this:

Education and Research
Education et Recherche

Note: This has been done without changing anything in the model (beyond setting True to localizable properties. This demonstrates the huge leverage effect that aspects can have on a CodeModeler model at BOM or persistence level.

The TextSearch Aspect

The Text Search Pattern adds methods to search an entity using tokenization techniques (also known as Middle-Of-Word text search technique). The search is case insensitive and accent insensitive. Say you want to create a screen in your application where users can search their contacts: the Text Search Aspect provides a way to implement it.

Note: This aspect is only supported using Microsoft SQL Server databases. It does not support entities with composite or object keys.

To enable the aspect, you must add the “Softfluent.TextSearch.xml” file.

For example, let’s define this model:

The TextSearch Aspect - Picture 337

And add the aspect:

The TextSearch Aspect - Picture 338

Now we can set the “Text Search” attribute to True in the property grid (“Aspects and Producers Properties” tab):

 The TextSearch Aspect - Picture 339

And set the “Extract” attribute to True for the Description, Email, FirstName, LastName properties, again using the property grid (“Aspects and Producers Properties” tab):

The TextSearch Aspect - Picture 340

After having built the project, you'll notice that, in the persistence layer:

  • a new table named TextSearch[KeyType] was created,

  • a new stored procedure named [EntityName]_TextSearchTokens was created.

Likewise, in the Business Object Model (BOM) the following classes were created:

  • Utilities\TextSearchEntityType.cs

  • Utilities\TextSearchUtilities.cs

  • Utilities\TextSearch[KeyType].cs (one per key type used),

  • Utilities\TextSearch[KeyType]Collection.cs (one per key type used).

In our example, the Contact entity is text searchable, so its resulting ContactCollection class will have two new methods:

  • TextSearchTokens(bool oneOrMore, string[] tokens)

  • PageTextSearchTokens(int pageIndex, int pageSize, CodeModeler.Runtime.PageOptions pageOptions, bool oneOrMore, string[] tokens)

The first one allows you to retrieve all results of the search, when the second one is a paged and sortable version of the first method.

Upgrading Non-Searchable Data

If you set-up the Text Search Aspect from the very beginning of your application's life, tokens will be created and deleted along their represented instance. However, if you add the Text Search Aspect on a database already containing data, you'll have to build all tokens for instances that where created prior to the addition of the aspect. In that matter, the TextSearchUtilities class provides a method named UpdateAll.

Create yourself a console application in which the UpdateAll method is called once per searchable class. For instance:

class Program
{
        static void Main(string[] args)
        {
             TextSearchUtilities.UpdateAll(typeof(ContactCollection));
        }
}

The AssociationManage Aspect

This aspect adds method(s) to all entities having at least one many to many relations.

Added methods enable association management without loading the corresponding collections. Such methods can be very useful when creating your user interface.

Using this aspect is straightforward: import the part in your project, specify it's run step, and it'll automatically be added on many to many entities.

To enable the aspect, you must add the “Softfluent.AssociationManage.xml” file.

In the end, a method will be added to each entity holding a many to many relation to another entity. This method will be named [RelationPropertyName]Manage:

public static bool ProductsManage(System.ComponentModel.CollectionChangeAction action, int orderId, int productId)
{
    if ((action == default(System.ComponentModel.CollectionChangeAction)))
    {
        throw new System.ArgumentNullException("action");
    }
    if ((orderId == CodeModelerPersistence.DefaultInt32Value))
    {
        throw new System.ArgumentNullException("orderId");
    }
    if ((productId == CodeModelerPersistence.DefaultInt32Value))
    {
        throw new System.ArgumentNullException("productId");
    }
    bool ret = CodeModelerPersistence.DefaultBooleanValue;
    CodeModeler.Runtime. CodeModelerPersistence persistence = CodeModelerContext.Get(Sample.Constants.SampleStoreName).Persistence;
    persistence.CreateStoredProcedureCommand(null, "Order", "ProductsManage");
    persistence.AddParameterEnumInt32("@action", action, new System.ComponentModel.CollectionChangeAction());
    persistence.AddParameter("@orderId", orderId);
    persistence.AddParameter("@productId", productId);
    System.Data.IDataReader reader = null;
    try
    {
        reader = persistence.ExecuteReader();
        if ((reader.Read() == true))
        {
            ret = ((bool)(ConvertUtilities.ChangeType(reader.GetValue(0), typeof(bool), null)));
        }
    }
    finally
    {
        if ((reader != null))
        {
            reader.Dispose();
        }
        persistence.CompleteCommand();
    }
    return ret;
}

The AutoFormattable Aspect

The AutoFormattable aspect adds enhanced formatting capabilities to entities.

At production time, the AutoFormattable aspect iterates on entities with the “Auto Formattable” attribute set to True (this is the default value) and adds an IFormattable implementation to the generated entity classes.

To enable the aspect, you must add the “Softfluent.AutoFormattable.xml” file.

The following example applies the AutoFormattable pattern at Salesman and Customer entity level (it’s also possible to set the “Is Auto Formattable” at project level to enable it for all entities):

The AutoFormattable Aspect - Picture 341

The AutoFormattable Aspect - Picture 342

ToString methods will be generated in the entity class as well as the collection class. Those methods will provide a string representation of the current class instance. The new methods are based on another class named FormatUtilities which is a CodeModeler-provided helper class that also gets generated by the aspect.

The FormatUtilities class provides a set of methods useful in order to format string representations of an instance of an entity (or a collection of entities). For instance, a developer can specify a format value such as "{0}:Name" which indicates to return the Name property value of the specified instance.

Furthermore, the format string supports dots such as "{0:Orders.Code}", or "{0:Orders.Products.Count}" which allows to navigate in the model to display hierarchical contents.

The HierarchyDeepLoad Aspect

This aspect adds a DeepLoadAll method on a given set of entities. The DeepLoadAll method loads a complete inheritance hierarchy using a single server call.

Note: This aspect only supports SQL Server targets.

To enable the aspect, you must add the “SoftFluent.HierarchyDeepLoad.xml” file.

For example, let’s define this model:

The HierarchyDeepLoad Aspect - Picture 343

And add the aspect. Now we can set the “Deep Load” attribute to True in the property grid (“Aspects and Producers Properties” tab):

The HierarchyDeepLoad Aspect - Picture 344

This is what the SQL producer adds, Customer_DeepLoadAllProc stored procedure that loads all records from User and Contractor tables:

CREATE PROCEDURE [Commerce].[User_DeepLoadAllProc]
AS
SET NOCOUNT ON
SELECT [User].[User_Id],[User].[User_Name],[User].[_typeName],[User].[_trackLastWriteTime],[User].[_trackCreationTime],[User].[_trackLastWriteUser],[User].[_trackCreationUser],[User].[_rowVersion]
FROM [Commerce].[User]
WHERE [User].[_typeName]='Commerce.User'
SELECT [User].[User_Id],[User].[User_Name],[User].[_typeName],[User].[_trackLastWriteTime],[User].[_trackCreationTime],[User].[_trackLastWriteUser],[User].[_trackCreationUser],[User].[_rowVersion],[Contractor].[Contractor_Level]
FROM [Commerce].[User],[Commerce].[Contractor]
WHERE [User].[_typeName]='Commerce.Contractor' AND [User].[User_Id]=[Contractor].[User_Id]

And the BOM producer adds a DeepLoadAll method to the UserCollection class that calls the Customer_DeepLoadAllProc stored procedure to load all User and Contractor instances.

public static Commerce.UserCollection DeepLoadAll()
{
    Commerce.UserCollection items = new Commerce.UserCollection();
    CodeModeler.Runtime.CodeModelerPersistence persistence = CodeModelerContext.Get(Commerce.Constants.CommerceStoreName).Persistence;
    persistence.CreateStoredProcedureCommand("Commerce.User_DeepLoadAllProc");
    using (System.Data.IDataReader reader = persistence.ExecuteReader())
    {
        do
        {
            while (reader.Read())
            {
                string typeName = CodeModelerPersistence.GetReaderValue(reader, "_typeName", (string)null);
                Commerce.User item = null;
                if (typeof(Commerce.Contractor).FullName == typeName)
                {
                    item = new Commerce.Contractor();
                }
                if (item == null)
                {
                    item = new Commerce.User();
                }
              ((CodeModeler.Runtime.ICodeModelerEntity)item).ReadRecord(reader);
                items.BaseAdd(item);
                item.EntityState = CodeModeler.Runtime.EntityState.Unchanged;
            }
        }
        while (reader.NextResult());
        CodeModeler.Runtime.CodeModelerPersistence.CompleteCommand(Commerce.Constants.CommerceStoreName);
    }
    return items;
}
Clone this wiki locally