OutPutFile delegate in Template class

Feb 1, 2009 at 8:32 AM
Oleg,

Could you spare some time to explain the OutPutFile delegate addition to Template class? I could image using this delegate while having a outputpath variable in the generator class, to pass a filename in order to calculate output path for the template. Is it what you were think when implementing it?


George J.
Coordinator
Feb 1, 2009 at 2:10 PM
The idea behind this delegate was to allow user of a code generator to customize where the output of individual templates is saved. For example, LinqToSqlSchemaGenerator creates a separate file for each table, each constraint and each procedure to match the approach used in Database Edition of Visual Studio and eventually add these files directly to the database project. However, for those who don't have Database Edition, such a large number of small files might be excessive and too difficult to manage. I could imagine someone wanting to generate the schema in a smaller number of files, perhaps one per table or even one per database. However, as long as name of the output file is hard coded inside of Generator.RunCore method, this customization would require user to recreate the entire generator class.

Instead of calling RenderToFile method, we can assign Template.OutputFile delegate in the constructor of the generator and call the Template.Render method (see LinqToSqlSchemaGenerator in source code). This gives the user ability to override this logic without having to recreate the entire generator.

// User generates schema in one sql file per table
LinqToSqlSchemaGenerator generator = new LinqToSqlSchemaGenerator();
generator.TableTemplate.OutputFile = () => generator.TableTemplate.Table.Name + ".sql";
generator.PrimaryKeyTemplate.OutputFile = () => generator.PrimaryKeyTemplate.Table.Name + ".sql";
generator.ForeignKeyTemplate.OutputFile = () => generator.ForeignKeyTemplate.Table.Name + ".sql";
//...
generator.Run();

To answer your question, I didn't think of having an OutputPath variable in the generator class. One generator could generate code in multiple different folders. In our SQL schema example, folder name could based on table name or it could be based on the type of schema object (table, constraint, stored procedure, etc). I think this knowledge belongs in the concrete Template class, not in the Generator.

When I've read your questions yesterday, it occurred to me that multiple properties of the concrete template might need to be dynamically recalculated, not just OutputFile. Turning all of them into delegates would be an overkill from complexity prospective. Alternatively, they could be simple properties if we provide a BeforeRender event in the Template class. So from user prospective, the scenario above would look like this:

LinqToSqlSchemaGenerator generator = new LinqToSqlSchemaGenerator();
generator.TableTemplate.BeforeRender += delegate (object sender, EventArgs e)
{
    generator.TableTemplate.OutputFile = generator.TableTemplate.Table.Name + ".sql";
}

generator.PrimaryKeyTemplate.BeforeRender += delegate (object sender, EventArgs e)
{
    generator.TableTemplate.OutputFile = generator.TableTemplate.Table.Name + ".sql";
}

generator.ForeignKeyTemplate.BeforeRender += delegate (object sender, EventArgs e)
{
    generator.TableTemplate.OutputFile = generator.TableTemplate.Table.Name + ".sql";
}
// ...
generator.Run();

Obviously, there is a lot more code compared to previous scenario, however, this would be a more traditional, easier to follow .NET component design. Also, if the number of "dynamic" properties is significant, it may be simpler to have a single event handler per template as opposed to a separate delegate for each "dynamic" property.

What are your thoughts on this? Which implementation scenario do you like better?
Feb 3, 2009 at 11:18 AM
I think T4Toolkit should be for anyone. So, both approaches should be available.

Although, I assume when someone starts with a new technology, one would use the simpler methods to achieve his goal. I would suggest the second method as the method to go.


George J.
Coordinator
Feb 3, 2009 at 1:37 PM
I was afraid you'd say so :). I liked the idea of using a OutputFile delegate because it is very compact. The original idea was to use something similar to a format string, such as "{Table.Name}.cs" where the {placeholders} would be automagically replaced with the actual property values. I didn't want to spend a lot of time on this, so lambda expressions seemed to be cheap way to get pretty close to the ideal without spending any development time. But you are right. Mascarading bare delegate as a property is more confusing. Besides, the second scenario can be rewritten like so:

generator.TableTemplate.BeforeRender += delegate { generator.TableTemplate.OutputFile = generator.TableTemplate.Table.Name + ".sql"; };

which is not much more verbose. Later we can decide if supporting {placeholders} in OutputFile property is a good idea, it should be possible to parse and emit lambda expressions to implement this. What do you think?
Coordinator
Feb 10, 2009 at 11:38 PM
George, I just checked this in. Give it a try.
Feb 11, 2009 at 6:39 AM
Ok! I am on it!


George J.