Skip to content
darkl edited this page Oct 28, 2011 · 3 revisions

Welcome to Flu.net! This tutorial will guide you how to implement your own fluent syntax in 3 simple steps!

Declare

Create a new project in your IDE (probably Visual Studio) and add a dll reference to Flunet.dll.

Add a new ".cs" file to your project and Declare the following interfaces in it (in our desired namespace):

[Inherited]
[Scope("DataTable")]
public interface IDataTableSyntax
{
    [UniqueInScope("DataTable")]
    [Alias("PrimaryKey")]
    IDataColumnSyntax<T> WithPrimaryKey<T>(string name);

    IDataColumnSyntax<T> WithColumn<T>(string name);
}

[Scope("DataColumn")]
public interface IDataColumnSyntax<T>
{
    [UniqueInScope("DataColumn")]
    IDataColumnSyntax<T> MakeNullable();

    [UniqueInScope("DataColumn")]
    IDataColumnSyntax<T> WithDefaultValue(T value);

    [UniqueInScope("DataTable")]
    [Alias("PrimaryKey")]
    IDataColumnSyntax<T> MakePrimaryKey();
}

This defines a fluent syntax for DataTable. It means that we will eventually be able to write something in the following fashion:

DataTable myTable = new DataTable("Books");
myTable.WithColumn<int>("ISBN").MakePrimaryKey()
.WithColumn<string>("Title")
.WithColumn<string>("Author")
.WithColumn<int>("Year");

The Scope attributes we used to decorate our types, are aliases for the type scopes. The UniqueInScope attributes on our methods will ensure that in a scope, we can call one of the methods with the same alias, at most once.

For example, in a DataTable fluent syntax, we won't be able to call MakePrimaryKey and WithPrimaryKey more than once, because they are unique in the DataTable scope and have the same alias.

In the DataColumn scope we can call WithDefaultValue at most once, and call MakeNullbale at most once.

Compile the project and move to the next step!

##Generate

Now we are going to Generate a fluent syntax from the declaration.

Run Flunet.Runner.exe with the following parameters:

  • /out:"DIRECTORY" - replace DIRECTORY with the path of the directory you want to generate the Fluent syntax to.
  • /assembly:"ASSEMBLY" - replace ASSEMBLY with the full path of the DLL you compiled in the previous step.
  • /type:"TYPE" - replace TYPE with the full name of the IDataTable interface you created in the previous step, for example: "Flunet.Samples.DataTable.IDataTableSyntax".
  • /class:"CLASS" - replace CLASS with the name you want the generated class to have, for example:"DataTableSyntax"
  • /namespace:"NAMESPACE" - replace with the namespace you want the generated class to have, for example: "Flunet.Samples.DataTable.Generated".

The runner will display a message it "generated successfully". (or throw an exception - in this case, please check all parameters are valid)

Check that in the output directory (the directory you mentioned in the /out: parameter) there has been created a file named CLASS.cs where CLASS is the name you passed to the /class: parameter.

##Implement

Now we need to write our implementation for the generated syntax.

Return to your IDE and add the generated CLASS.cs file to your project (or to another project, it doesn't matter)

Create a new .cs file in the same project and write in it the following: (You will need to replace "DataTableSyntax" with your CLASS name and add this class to your NAMESPACE)

public partial class DataTableSyntax
{
    internal class DataTableSyntaxImplementer<T> : SyntaxImplementer<T>
    {
        #region Members

        private DataTable mTable;
        private DataColumn mColumn;

        #endregion

        #region Constructor

        public DataTableSyntaxImplementer(DataTable table)
        {
            mTable = table;
        }

        private DataTableSyntaxImplementer()
        {
        }

        #endregion

        #region Inner implementation

        protected override SyntaxImplementer<T1> InnerWithColumn<T1>(string name)
        {
            DataColumn column = mTable.Columns.Add(name, typeof(T1));

            return new DataTableSyntaxImplementer<T1>
            {
                mTable = mTable,
                mColumn = column
            };
        }

        protected override SyntaxImplementer<T> InnerMakeNullable()
        {
            mColumn.AllowDBNull = true;
            return this;
        }

        protected override SyntaxImplementer<T> InnerWithDefaultValue(T value)
        {
            mColumn.DefaultValue = value;
            return this;
        }

        protected override SyntaxImplementer<T> InnerMakePrimaryKey()
        {
            mTable.PrimaryKey = new[] { mColumn };
            return this;
        }

        protected override SyntaxImplementer<T1> InnerWithPrimaryKey<T1>(string name)
        {
            return ((DataTableSyntaxImplementer<T1>)InnerWithColumn<T1>(name)).InnerMakePrimaryKey();
        }

        #endregion
    }    
}

All we had to implement is the functions we declared in our interfaces.

In order to be able to use our syntax, we'll need to add a few extension methods:

public static class DataTableExtensions
{
    public static DataTableSyntax.IState2<T> WithColumn<T>(this DataTable dataTable, string name)
    {
        DataTableSyntax.IState0 syntax = new DataTableSyntax.DataTableSyntaxImplementer<T>(dataTable);
        return syntax.WithColumn<T>(name);
    }
}

Now we will be able to write the following:

DataTable myTable = new DataTable("Books");
myTable.WithColumn<int>("ISBN").MakePrimaryKey()
.WithColumn<string>("Title")
.WithColumn<string>("Author").MakeNullable()
.WithColumn<int>("Year").WithDefaultValue(2011);

Clone this wiki locally