Tag Archives: Fluent Api

PetaPoco – Convention Based Fluent Mapping

After using the Fluent Mapping’s I blogged about a few months ago, I started to see patterns occurring. My table names were always the pluralized type and the primary key was the Type name concatenated with “Id”. With all this repetition, I decided that conventional mappings were needed and that they should be overridable for the edge case.

Here is the simplest example.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
    });
}

This just tells the FluentMapping to scan the assembly with the type MvcApplication in it and any class it finds, define a mapping so that the object will can be persisted to PetaPoco. Now by itself this is pretty pointless because by default PetaPoco already does this for you on the fly. It will use the same defaults PetaPoco currently invokes which is

  1. TableName = Type name
  2. PrimaryKey = “ID”
  3. PrimaryKeyAutoIncrement = on

You would also not want add every type in your project either as most of them will not be used for database.

Ok, so we don’t want all the types, but we want to filter them by their namespace. All my DB models are in Models/Db, which has the namespace MvcApplication.Models.Db so we will use this as the filter.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
        x.IncludeTypes(type => type.Namespace == "MvcApplication.Models.Db");
    });
}

The IncludeTypes method takes in a lambda expression with the Type as the argument and returns a bool. This method can also be used to exclude types.

So with all our DB models now being mapped using defaults, lets change them. Say that all our tables have the prefix “t_” on them because our DB admin likes hungarian notation. He also wants the primary key to auto increment (identity column) and the column name to end with “Id”. Ok mister Db man….you’re the boss.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
        x.IncludeTypes(type => type.Namespace == "MvcApplication.Models.Db");
        x.TablesNamed(type => "t_" + type);
        x.PrimaryKeysNamed(type => type + "Id");
        x.PrimaryKeysAutoIncremented(type => true);
    });
}

Too easy.

Next we can configure how columns are mapped. By default the column name will map to the property name, but it doesn’t have to. Our Db man says all columns should have the “d_” prefix if the column is a DateTime property, and the rest should remain the same as the property name.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
        x.IncludeTypes(type => type.Namespace == "MvcApplication.Models.Db");
        x.TablesNamed(type => "t_" + type);
        x.PrimaryKeysNamed(type => type + "Id");
        x.PrimaryKeysAutoIncremented(type => true);
        x.Columns.Named(prop =>
            prop.PropertyType == typeof(DateTime) ? "d_" + prop.Name : prop.Name);
    });
}

I think you get the idea. You can also Ignore properties, set a Version columns and set a Result column.

There are also two extension methods that set some smart defaults. These are:

  1. Tables are the pluralized version of the Type name
  2. PrimaryKeys are the Type name concatenated with “Id”
  3. All complex properties (classes) are ignored

These can be set simply like this.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
        x.IncludeTypes(type => type.Namespace == "MvcApplication.Models.Db");
        x.WithSmartConventions();
    });
}

You can create your own extension methods as well. They are extremely easy. Check out the code for the WithSmartConventions to see how easy it is.

There is one last thing. Conventions are great, but if you can’t override them then they’re pretty useless, because there is always an exception to the rule. Mr Db man comes to me and says that there is one table which is used by 5 systems and does not play by our conventions we have setup. The Type name is Abc, but the table name needs to be “Abcies” not “Abcs”, and the primary key is “MyAbcId”. He also doesn’t want to map the Name column.

protected void Application_Start()
{
    FluentMappingConfiguration.Scan(x =>
    {
        x.Assembly(typeof(MvcApplication).Assembly);
        x.IncludeTypes(type => type.Namespace == "MvcApplication.Models.Db");
        x.TablesNamed(type => "t_" + type);
        x.PrimaryKeysNamed(type => type + "Id");
        x.PrimaryKeysAutoIncremented(type => true);
        x.Columns.Named(prop =>
            prop.PropertyType == typeof(DateTime) ? "d_" + prop.Name : prop.Name);

        x.OverrideMappingsWith(new MappingOverrides());
    });
}

public class MappingOverrides : Mappings
{
    public MappingOverrides()
    {
        For<Abc>().TableName("Abcies")
                    .PrimaryKey("MyAbcId")
                    .Columns(x => x.Column(y=>y.Name).Ignore());
    }
}

Any mappings defined in our mapping overrides will be overlayed on top of our conventions. We covered 99% of our mappings with these conventions but still needed to manually configure one table.

I hope you can see how powerful this can be, especially when you’re got more than 100 tables.

This is currently only available on my branch, which can be found https://github.com/schotime/PetaPoco and downloaded Schotime-PetaPoco-4.0.3.11.zip.

Let me know how you get on or if you have any issues.

Adam

Fluent PetaPoco – External Mappings

Update 20/Nov/2011: This has been now added to my master branch and is available here for download as of 4.0.3.5. https://github.com/schotime/PetaPoco/downloads

The other day I set out to build another way to configure the mappings for PetaPoco. Here is how you currently configure a mapping using attributes.

    [TableName("AttribPocos")]
    [PrimaryKey("Id")]
    public class attribpoco
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Result { get; set; }

        [Ignore]
        public int IgnoreColumn { get; set; }
    }


This is pretty easy and requires only a couple of attributes. After you do a few of these, sometimes I wish I could set all the mappings in one place, so here is the same mappings but with my new configuration. Method 1.

    public class MyMappings : PetaPocoMappings
    {
        public MyMappings()
        {
            For<attribpoco>()
                .TableName("AttribPocos")
                .PrimaryKey(x => x.Id)
                .Columns(x =>
                             {
                                 x.Ignore(y => y.IgnoreColumn);
                             });
        }
    }


So you can define multiple mappings here with new For<> statements. If you want to be more explicit and have one mapping class per mapping you can do the following. Method 2.

    public class AttribPocoMap : PetaPocoMap<attribpoco>
    {
        public AttribPocoMap()
        {
            TableName("AttribPocos");
            PrimaryKey(x => x.Id);
            Columns(x =>
                        {
                            x.Ignore(y => y.IgnoreColumn);
                        });
        }
    }

Both ways of mapping have the same API, so they can be interchanged, however when hooking the mappings into PetaPoco, things differ a little.

You need to configure the mappings just once in your application so a good place to do that in a web application is the Application_Start() method in the global.asax.cs. Here is how you do this for the first method of mapping configuration.

PetaPoco.FluentMappings.Configuration.Configure(new MyMappings());

For method 2 you have to be a little more explicit at the moment. For each mapping you need to include it as a parameter to Configure.

PetaPoco.FluentMappings.Configuration.Configure(new AttribPocoMap(),…);

Also, just because you have used the fluent mappings for one class, you can use the attribute mapping style at any time as it will automatically default back to the attribute mappings if you don’t have a fluent mapping configured. You also don’t have to define all the columns. By default all columns are mapped unless you are using explicit mappings or you have used the Ignore or Result column like above.

Currently all features supported by attributes in my branch are supported in the fluent mappings. eg. Sequences, Explicit Columns, Composite Keys, Versioning, Ignore Columns and Result Columns.

If you would like to give it a try you can download the my PetaPoco branch here:

https://github.com/schotime/PetaPoco/downloads

Let me know what you think.

Adam