Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy

Introduction

Schema class is a data structure that stays between classes and tables, between fields and columns, between references and foreign keys. This structure is the only authority any time the engine needs mapping information like... What is the column name of a field?

The main source of information for generating this mapping is your entities themselves. SchemaBuilder is the class that takes your entities as an input and generates a Schema as an output. That means that generating the schema is usually nothing more than two or three lines of code on your Global.asax (or just when loading your application) and it will do it just fine while you evolve your application.

   public static void Start(string connectionString)
   {
       SchemaBuilder sb = new SchemaBuilder();
       sb.Include<CustomerDN>();
       ConnectionScope.Default = new Connection(connectionString, sb.Schema);
   }

The information stored in the Schema structure could be re-calculated each time its needed, but schema pay for their own existence:

  • Flexibility: Having data instead of rules to map entities and tables allows much more flexibility modifying the end result, and allows adding another schema provider in the future (like Xml, just in case someone likes that).
  • Simplicity of code: Having the answer pre-calculated somewhere, instead of recalculating it, makes engine code simpler.
  • Increased performance: Avoids reflection and has the answer already available for the engine inner classes.

Normal usage of SchemaBuilder

Usually, SchemaBuilder is just a utility class you just use to fill with some (or all) of your entities to be able to use the engine on them. The process can be resumed in three simple steps:

  1. Create a SchemaBuilder
  2. Fill the builder with some entities using some of the IncludeXXX family methods. A Schema embryo will be growing inside.
  3. Create a new Connection with the newly born Schema created SchemaBuilders and set it as the default connection. See more about Connection.

Including entities is the process of inserting Tables in the Schema by including their types. Any time a type is included, all the related entities that are reachable from it are included as well to avoid inconsistent states. There are however different flavours of IncludeXXX method to make your life even easier. Let's see out options to include entities in the Schema.

  • Table Include(Type type): Includes just this type (and the related entities...)
  • Table Include<T>(): Same as above, but in a generic fashion.
  • void IncludeSimilars(Type example): Includes all the entities in the same assembly and namespace than the example that inherit from IdentifiableEntity and have no Ignore Attribute.
  • void IncludeSimilars<T>() where T : IdentifiableEntity:} Same as above but in a generic fashion.
  • void IncludeAll(Assembly assembly): Includes all the entities in an assembly that inherit from IdentifiableEntity and have no Ignore Attribute.

Easy.

Customizing the mapping (Advanced Topic)

The process above is ok while you want normal mapping of your entities, but if the default table or column names doesn't follow your company standards, or you are planning to reuse some assembly with entities from one project, but changing the mapping, we provide three ways to change de default Schema mapping.

There are three different ways of customizing Entities-Database mapping in Signum Engine:

SchemaBuilderSettings: Override your entities Attributes

To remove duplication and centralize relateded information, Signum Framework uses attributes and the field/class declaration itself to deduce database schema information.

In some situations, however, this solution is not very flexible. i.e: You cold have a really nice Authorization module, but you can't reuse it in a new project because your UserDN entity is pointing to a concrete EmployeeDN of the old project, and instead of linking this library you have to copy paste all of it.

SchemaBuilderSettings allows you to add or remove Attributes on field or types at runtime to customize entities that you don't own or where you don't want to change the actual code.

If your can reduce your modifications to just change some field or type attribute (or remove a field or type by adding an Ignore attribute) to change the mapping, then you could use this code without actually changing the used code. If you want to change the code in more depth you will have to copy and paste. Sorry, C# is a static language.

Let's see an example: You have done a ERP for a Zoo already, and you end up with a nice AnimalsDN.dll assembly with LionDN, GiraffeDN and MonkeyDN inheriting from AnimalDN.

    [Serializable]
    [ImplementedBy(typeof(GiraffeDN), typeof(LionDN), typeof(MonkeyDN)]
    public class AnimalDN : Entity
    {
        (...)
    }

[Serializable] public class GiraffeDN : AnimalDN { (...) }

[Serializable] public class LionDN : AnimalDN { (...) }

[Serializable] public class MonkeyDN : AnimalDN { (...) }

Because the project has been a big success (it couldn't be a failure if you do it with Signum Framework) now an Aquarium is asking you for a similar program. You can save the AnimalDN entity, and all the code that deals with animals in a generic way, but the actual animals are now different:

     [Serializable]
    public class DolphinDN : AnimalDN { (...) }

[Serializable] public class SharkDN : AnimalDN { (...) }

[Serializable] public class BelugaDN : AnimalDN { (...) }


In order to mix the code at database level, you need to make some changes to your Start method:

    private static void Start(string connectionString)
    {
        SchemaBuilderSettings sbs = new SchemaBuilderSettings(); 

sbs.OverrideTypeAttributes<AnimalDN>(new ImplementedByAttribute(typeof(DolphinDN), typeof(SharkDN), typeof(BelugaDN))); sbs.OverrideFieldAttributes<BelugaDN>(a=>a.ToStr, new SqlDbTypeAttribute{ Size = 500});

SchemaBuilder sb = new SchemaBuilder(sbs); sb.Include<AnimalDN>();

ConnectionScope.Default = new Connection(connectionString, sb.Schema); }

As you see, we have overridden the legacy ImplementedByAttribute of AnimalDN by a new one that includes the newly created water animals, so all the previous references pointing to AnimalDN will now go for the new ones.

Also, because Belugas have an important special illness that has to be shown on ToString method, we have increased the size or ToStr just for Belugas (and animals inheriting from beluga).

Some important thing to Notice:

  • The Attribute replacement is 'virtual'. Only SchemaBuilder will take care of it, since it's not possible to actually override Attributes in the CLR. It doesn't work on validator.
  • Once a Type or a field is overridden, it overrides all the attributes, so if there are 3 attributes and you want to override just one, remember to copy the other two.
  • As you have seen in the example, this technique also allows you to change attributes on an inherited field for a particular subclass, something that is not available in the CLR.

In the OverrideFieldAttributes example we use a lambda to identify the FieldInfo, we call this technique strong-typed reflection and is nice because it allows refactorings. Se more of this in Reflection

Change the generated Schema

Once you have finished including entities in the SchemaBuilder, you have a new Schema in the Schema property.

You can change whatever you want in this structure, and it will be reflected whenever you use the engine (thought Database or Administrator).

Important Note: There's just no validation on Schema data structure, make modifications at your own risk and don't expect a nice exception message to be thrown if you do something silly.

Internally, a Schema is just a Dictionary<Type, Table>.

Table is the class that maps an IdentifiableEntity to database table. Contains a Type, a Name, an Identitiy flag, and two dictionaries, one for Fields and one for Columns.

Usually, the underlying objects of these dictionaries are the same, depending of the Field Type:

  • PrimaryKeyField, ValueField and RefenrenceField are a Fields and also IColumns.
  • ImplementedByField and ImplementedByAllField contain more than one IColumn.
  • EmbeddedField contains a nested Dictionary of Fields (with their own IColumns).
  • CollectionField has no IColumn at all, instead has a RelationalTable object.

Maybe the following diagram clarifies the classes that shape the Schema:

Image

The architecture is a bit complex because it has to be in the middle between the entities and the tables, but it is honest and convenient to use.

To make the navigation through the entities of your Schema easier, an advanced strong-typed reflection API is available.

For example, if you have some entities like this:

    [Serializable]
    public class BillDN : Entity
    {
        MList<BillLineDN> lines;
        public MList<BillLineDN> Lines {...}
    }

[Serializable] public class BillLineDN : EmbeddedEntity { int quantity; public int Quantity {...}

ProductDN product; public ProductDN Product {...} }

[Serializable] public class ProductDN : Entity { }


And if for some reason you want to change Quantity column from Int to SmallInt in the database, while preserving it as int on the entity, you will need to do something like this:

   Schema s = sb.Schema;

Table billTable = s.Table(typeof(BillDN)); CollectionField linesField = (CollectionField)billTable.Fields["lines"]; RelationalTable linesTable = linesField.RelationalTable; EmbeddedField billLineField = (EmbeddedField)linesTable.Field; ValueField quantityField = (ValueField)billTable.Fields["quantity"]; quantityField.SqlDbType = SqlDbType.SmallInt;

ConnectionScope.Default = new Connection(connectionString, s);

But this code is tooo long and error-prone, instead you can use the much cleaner and strong-typed version:

   Schema s = sb.Schema;

s.Field<ValueField, BillDN>(b => b.Lines.First().Quantity).SqlDbType = SqlDbType.SmallInt;;

ConnectionScope.Default = new Connection(connectionString, s);

Cool, isn't it?

Override SchemaBuilder behaviour

The last but not least way of customizing the Schema is to inherit from SchemaBuilder and create your own SchemaBuilder class.

Let's say you want to clean-up your table names. For an entity like ProductDN you want the table to be named, not tlProductDN (default behavior) but just Product, removing the prefix and your custom postfix (like DN).

You can do that just by creating your own MyCustomSchemaBuilder and overriding GenerateTableName method. Since the base method is implemented like this (It's an open source project!!) :

    public virtual string GenerateTableName(Type type)
    {
        return "tl" + TypeName(type);
    }

you just have to write:

    public class MyCustomSchemaBuilder : SchemaBuilder
    {
        public override string GenerateTableName(Type type)
        {
            string name = TypeName(type);
            if (name.EndsWith("DN"))
                return name.Substring(0, name.Length - 2);
            else
                return name;
        }
    }


And MyCustomSchemaBuilder instead of SchemaBuilder in your Start method.

You can do more advanced things using this technique, if you are curious look at the implementation of SchemaBuilder.
Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.