Wednesday, February 6, 2019

CompileTimeWeaver.Fody V3.1.7

(Please reference to document for newer version 3.3.3 with bug fix and enhancements)

The nuget package 

PM> Install-Package CompileTimeWeaver.Fody

.Net Platforms

  • Framework 4.6.1
  • NetStandard 2.0

Compile time IL rewriting tool for AOP implementation

Different from runtime interception with dynamic proxy, this tool rewrites assembly at VisualStudio.Net build time, so that your code get these achievements that dynamic proxy based enhancement cannot give:
  • Much better performance, no dynamic proxy, no thread block or reflection at run time.
  • Directly instantiate your weaved classes with new operator
  • weave virtual methods/properties
  • weave static methods/properties
  • weave  extension methods
  • weave constructors

Advice Based Programming Model

Start with version 2, this tool support advice based programming model. Advice based programming model has these superiorities comparing to the old decorator based programing model in version 1:
  • Easy to intercept async method without thread blocking
  • Simple to control the flow and add “before”, “after”, “around” and “exception” advices
  • Allow to advise methods with parameters of reference type, value type and generic type, ref parameters and out parameters are allowed, too.

Your Code

public class MyAdvice : AdviceAttribute
{
    public override object Advise(IInvocation invocation)
    {
        // do something before target method is Called
        // ...
        Trace.WriteLine("Entering " + invocation.Method.Name);

        try
        {
            return invocation.Proceed();    // call the next advice in the "chain" of advice pipeline, or call target method
        }
        catch (Exception e)
        {
            // do something when target method throws exception
            // ...
            Trace.WriteLine("MyAdvice catches an exception: " + e.Message);
            throw;
        }
        finally
        {
            // do something after target method is Called
            // ...
            Trace.WriteLine("Leaving " + invocation.Method.Name);
        }
    }

    public override async Task<object> AdviseAsync(IInvocation invocation)
    {
        // do something before target method is Called
        // ...
        Trace.WriteLine("Entering async " + invocation.Method.Name);

        try
        {
            return await invocation.ProceedAsync().ConfigureAwait(false);   // asynchroniously call the next advice in advice pipeline, or call target method
        }
        catch (Exception e)
        {
            // do something when target method throws exception
            // ...
            Trace.WriteLine("MyAdvice catches an exception: " + e.Message);
            throw;
        }
        finally
        {
            // do something after target method is Called
            // ...
            Trace.WriteLine("Leaving async " + invocation.Method.Name);
        }
    }
}

[MyAdvice]
public class MyClass
{
    public int Add(int x, int y)
    {
        return x + y;
    }

    public Task<int> AddAsync(int x, int y)
    {
        await Task.Dely(1000).ConfigureAwait(false);
        return x + y;
    }
}
The first time when you compile MyClass class, FodyWeavers.xml and FodyWeavers.xsd are generated if they do not exist yet. FodyWeavers.xml content likes this below:
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <CompileTimeWeaver />
</Weavers>
If (1) your MyClass and MyAdvice are in different assemblies and (2) your target platform is netstandard, you need
to manually add the full name of MyAdvice class into AdviceAttributes property of CompileTimeWeaver section in FodyWeavers.xml file
as below (suppose the namespace of MyAdvice is Advices here):
  <CompileTimeWeaver AdviceAttributes="Advices.MyAdvice" />
To add more advice classes into AdviceAttributes property, seperate them with ‘|’ delimitor, for example: AdviceAttributes=“Advices.MyAdvice1|Advices.MyAdvice2”.
You do not have to change this file if your target is .net Framework or MyClass and MyAdvice are in same assembly.

Notes:

  • When multiple advices are applied to a method, the advices are invoked as a pipe line, the Advise() or AdviseAsync() of the first advice is called first.
  • When an advice is applied to class level, it is identical to this advice is added on each constructor and each method.
  • Class level advices appears before all constructor/method level advices
  • Each advice is assigned an Order, it is the sequence number of the appearance in the type group.
  • Each advice on a recursive method is invoked only when the method was entered at the very first time, re-entrances don’t invoke the advice again, for performance reason.

NHibernate Transaction Awareness Framework V3.9.1

nuget package

.Net Platforms:
  • Framework 4.6.1
  • NetStandard 2.0

NHibernate Transaction Awareness Framework

NHibernate Transaction Awareness Framework is implemented by CompileTimeWeaver.Fody https://nuget.org/packages/CompileTimeWeaver.Fody to gain the best performance. It removes the burden of NHibernate developers of managing sessions and transactions, and domain model validations.

1. Transaction-aware Object

A Transaction-aware Object is a .net object that automatically execute within the scope of a NHibernate session, it participates in an existing or ongoing session, or be the root of a new session. Similar to enterprise component in .net or session bean in EJB, a Transaction-aware Object is a business logic object accessing domain objects (or entity objects). The following example shows a Transaction-aware c# class:
    [TransactionAware(DbName="CustomerDb")]
    public class MyClass: IDisposable
    {
        public async Task TravelAllPersonsAsync()
        {
            var session = TransactionContext.GetCurrentSession("CustomerDb");

            var persons = await session.Query<Person>().ToListAsync().ConfigureAwait(false);
            foreach (var person in persons)
            {
                Trace.WriteLine(person.Id + " - " + person.FirstName);
            }
        }

        public void Dispose()
        {
            //do nothing
        }
    }
You see there is nothing special except the C# class is decorated by TransactionAwareAttribute. In the TravelAllPersonsAsync() method above, developers can write code to read or write database with NHibernate session without think about when the transaction is commited or when the session is opened/closed, the framework manages the session lift-cycle and transaction lift-cycle automatically.

2. Transaction-aware methods

A method is Transaction-aware method if:
(1) it is a non-static method, and
(2) it is decorated by TransactionAwareAttribute, or the class is decorated by TransactionAwareAttribute.
A Transaction-aware method can either be sync method or async method since version 3.9, and a Transaction-aware method is not thread-safe. A transaction-aware method can call static TransactionContext.GetCurrentSession() method to get current context session, and then take full advantage of NHibernate powers to access database, but a transaction-aware object is not allowed to close context session explicitly. Below is correct code snippet to access database through context session:
    ISession session = TransactionContext..GetCurrentSession("CustomerDb");
    var user1 = session.Get<User>(1);
    user1.Name = "Simon Lu";
    session.Update(users);

3. Requirements of a Transaction-aware Object

A transaction-aware object is an instance of an ordinary .net class meeting a few simple requirements:
  • Decorated with TransactionAwareAttribute.
  • Implement IDisposable interface, the void Dispose() method must exist event though it is empty.
  • Call TransactionContext’s methods in transaction-aware methods only.
  • Don’t explicitly close session or commit/rollback transaction

4. Transaction

Because framework manages the life-time of transactions, developers don’t need to start a transaction and commit/roolback it in the code. The framework makes the transaction control more easy with these two programming interfaces:

Using AutoComplete

The AutoCompleteAttribute indicates the Debit() method below to abort the transaction if any exception is thrown, and commits the transaction otherwise.
[TransactionAware(DbName="CustomerDb")]
public class Account
{
    [AutoComplete]
    public virtual void Debit(int amount)
    {
        // Do some database work. Any exception thrown here aborts the transaction;
        // otherwise, transaction commits.
    }
}

Voting in an Automatic Transaction

The following code fragment shows the SetAbort and SetComplete methods in use.
//Try to do something crucial to the transaction in progress.
if( !DoSomeWork() )
{
  //Something goes wrong.
  TransactionContext.SetAbort();
}
else
{
  //All goes well.
  TransactionContext.SetComplete();
}
SetComplete indicates that your method votes to commit its work; SetAbort indicates that your object encountered a problem and votes to abort the ongoing transaction, a single abort vote from any method participating in the transaction causes the entire transaction to fail.
The absence of an explicit vote in a method means the database update in the method accumulate the context transaction without commit or rollback. If there is not explicit vote during the life-cycle of the owner of a context session, the context transaction is rollback when the owner is disposed, other than that, a context transaction is neither committed nor aborted until a transaction-aware method explicitly vote.

5. Join existing session or open new session

  [TransactionAware(DbName="your_database_name", TransactionAwareOption = TransactionAwareOption.Required)]
  public class MyClass: IDisposable
  {
        ...
  }
When applying the TransactionAwareAttribute, you can set TransactionAwareOption property to control if the object joins existing session or open a new session.
  • TransactionAwareOption.Required (default)
    Indicates that the object requires a session. It runs in the scope of an existing session, if one exists. If no session exists, the object starts one.
  • TransactionAwareOption.RequiresNew
    Indicates that the object requires a new session.

6. Object Injection

A Transaction-aware object can inject objects as its properties or fields by declare the properties/fields with InjectedOnCreatedAttribute, and transparently dispose them when it is disposed. This open the door for developers to inject Transaction-aware objects into the other Transaction-aware object so that they can share the same database session. Below is an example class, and instances of EntityRepository is created and injected as private field _entityRepository, and it is disposed automatically even though the Dispose method body is empty.
    [TransactionAware(DbName="your_database_name")]
    public class TransactionAwareUnitOfWork : IDisposable
    {
        [InjectedOnCreated]
        private EntityRepository _entityRepository;
        ...
        public void Dispose()
        {
            //Transaction Awareness framework will automatically dispose all injected objects
        }
    }

7. Practice

Prerequisite: domain classes and mapping classes are ready.
Step 1) Download and add NuGet package CompileTimeAOP.TransactionAwareness to your c# project.
Step 2) Add a Transaction-aware class to your project
    using CompileTimeAOP.Transaction;

    [TransactionAware(DbName="your_database_name")]
    public class MyClass: IDisposable
    {
        public async Task TravelAllPersonsAsync()
        {
            var session = TransactionContext.GetCurrentSession("your_database_name");

            var persons = await session.Query<Person>().ToListAsync().ConfigureAwait(false);
            foreach (var person in persons)
            {
                Trace.WriteLine(person.Id + " - " + person.FirstName);
            }
        }

        public void Dispose()
        {
            //do nothing
        }
    }
Step 3) Add this code snippet into application startup code to setup database connection
    using CompileTimeAOP.Transaction;

    DbSessionFactory.Create("your_database_name",
                                    "hibernate.yourdb.config",
                                    fluentCfg =>
                                    {
                                        fluentCfg.Mappings(m =>
                                                           {
                                                               m.FluentMappings.AddFromAssemblyOf<YourFluentNHibernateMapClass>();
                                                           });
                                        fluentCfg.ExposeConfiguration(cfg => new NHibernate.Tool.hbm2ddl.SchemaUpdate(cfg).Execute(false, true));
                                    });
Step 4) Use Transaction-aware class
            using (var testObj = new MyClass())
            {
                await testObj.TravelAllPersonsAsync();
            }

8. Multiple Database

Sometimes it is necessary for a Transaction-aware object to access two databases in one method, for example, an online shopping cart object updates transaction database and access customer service database in its PlaceOrder () method. Transaction-awareness framework let it do it easily by decorating the PlaceOrder method with two TransactionAwareAttribute with different database names as constructor parameter. See it below:
    [TransactionAware("TransactionDb"]
    public class OnlineShoppingStore: IDisposable
    {
        [TransactionAware("CustomerDb"]
        [AutoComplete]
        public void PlaceOrder(OrderDto order)
        {
            var customerDbSession = TransactionContext.GetCurrentSession("CustomerDb");
            var customer = customerDbSession.Query<CustomerEntity>(order.CustomerId);
            ...
            var transDbSession = TransactionContext.GetCurrentSession("TransactionDb");
            transDbSession.Save(new OrderEntity(){...});
        }
        ...
    }
The framework comes with out-of-the-box Distributed-transaction feature, in the PlaceOrdermethod above, it puts two database update in one distributed transaction so that the integrity accross multi-database is guaranteed.
Notes:
  • use TransactionContext.GetCurrentSession(string dbName) to get database sessions from different database where multiple databases exist.
  • Call DbSessionFactory.Create() for each database with different database names to setup connection.
  • Distributed-transaction needs DTS Windows service, you may change firewall settings if two database servers are on different computers. (study how to setup DTS on two servers)

9. Fluent Validation

Transaction-aware framework offers developers out-of-the-box FluentValidation feature to guarantee the integrity of domain model whenever there is database update. It allows to put all model validation rules in different assembly to avoid domain model polution.
Usage:
  • Create a C# project to contain all the FluentValidation validators.
  • Add all validators to validate entity classes, for example, the PersonValidator class below to validate Person entity class:
    using FluentValidation;

    public class PersonValidator : AbstractValidator<Person>
    {
        public PersonValidator()
        {
            RuleFor(x => x.Id).NotEmpty();
            RuleFor(x => x.FirstName).NotNull().Length(1, 10);
        }
    }
  • Add validator assembly, typeof (PersonValidator).Assembly, to the call to DbSessionFactory.Create() as the last parameter, the framework validates entity objects once there is update on them, and throws FluentValidation.ValidationException exception if there is any break to validation rules.
    DbSessionFactory.Create("your_database_name",
                                    "hibernate.yourdb.config",
                                    fluentCfg =>
                                    {
                                        fluentCfg.Mappings(m =>
                                                           {
                                                               m.FluentMappings.AddFromAssemblyOf<YourFluentNHibernateMapClass>();
                                                           });
                                        fluentCfg.ExposeConfiguration(cfg => new NHibernate.Tool.hbm2ddl.SchemaUpdate(cfg).Execute(false, true));
                                    },
                                    typeof (PersonValidator).Assembly);