T4, T4 ToolBox, Linq To Sql Entity Base, Code Description

 

Download Link —

The code can be downloaded at the following link.

https://mkamoski1.wordpress.com/2009/12/01/t4-t4-toolbox-linq-to-sql-entity-base-code-sample/

Credits —

I want to give special thanks to the following people, without whom this sample would not exist.

This is in no particular order, and certainly not a complete list, please note the following superstars…

Oleg Sych
http://www.olegsych.com/
http://www.codeplex.com/t4toolbox

Matthew Hunter
http://complexitykills.blogspot.com/
http://www.codeplex.com/LINQ2SQLEB/

Kristofer Andersson
http://www.huagati.com/

Frans Bouma
http://weblogs.asp.net/fbouma/
http://www.llblgen.com/

(BTW, one would probably learn more from studying their work rather than mine.)

Purpose —

The purpose of this article is to show one way to use T4 templates and T4ToolBox and LinqToSqlEntityBase and Repository and other nice things to help one code a bit faster.

Warning —

I trt to follow, a bit, ideas from GEFN and RAD and YAGNI and a few other quick-mode development ideas. I try to Agile, especially bold, and I refactor heavily. See this List Of Software Development Philosophies for some general details on all this. However, even in doing so, I only do it loosely. Etc. As such, my style is a bit loose and it may not be for everyone. Also, please note that this is a work in progress. APIs may change. Interfaces may change. I have the luxury of working on my own projects from start-to-finish, so I have no compunction in making big changes if necessary. I do, however, have had this code in production as of 2009-Oct-01 and it will probably remain in production until about 2010-Apr-01, when VS.NET 2010 is released and I get it and I implement Self-Tracking-Entities and etc.

Problem —

Linq To Sql was bothering me. I wanted to use Linq-To-Sql in a disconnected way. I did not like the way that the DataContext was getting littered about my layers. I did not like the way that Linq entities had to have reference to an active DataContext object in some scenarios. I did not like the way the baseline CRUD functionality required using some methods of the DataContext and some Entity static methods. Etc. I wanted a Repository pattern, or a quasi-one. I wanted a “Manager” that could “do all the work” and an Entity that could “hold all the data”. I wanted to hand an Entity to my Manager and say “Create” or “Update”. Etc.

Writing the same code was bothering me. I had CRUD methods that I wanted to be generated rather than write by hand. I wanted to use code generation. It did not look like I would be able to get a license to CodeSmith at my current gig. The MyGeneration project, an old favorite, seemed a bit stale. The available code generators were WAY too much for me. I wanted something simple. The generator that I wrote myself was pretty ugly and it was not powerful enough. I did not want a black-box generator. I wanted to generate code that could, if necessary,  have the code generation engine unplugged and the code should be human-readable and not too far from what I would write by hand manually. (Of course, my goal is not to have to unplug the code generator, but if something strange happens, such as a fatal flaw in the generator, then the fall-back must be to manage the code by hand.)

Handling built-in system-level code-table values was bothering me. The common example is “Roles”. I needed a generated output class that could help. I wanted no hardcode for such values. See below for more on this topic.

Design —

I decided to build some simple T4 templates to do the work. The T4ToolBox was a nice way to get up and running quickly. Futhermore, there were some nice tutorials to follow, from Oleg Sych. In my design, there would be one, project-specific “settings template”, that would define the varying parameters for a given project, and this settings-template could not be reused across more than one project. The core templates, these must be able to be reused on any number of projects. 

I decided to use the “one Manager per database table or view” approach. Nice and simple. I would handle full-object-load and cascade-delete and etc on an as-needed basis. The generated Manager would have core CRUD operations. The hand-extended Manager partial class would have custom logic.

I decided to use the “one Entity per database table or view” approach. Nice and simple. These would just hold data. For the most part, the generated Linq To Sql entities, that are made by the VS.NET Designer, were fine. I extended using partial classes for some of the extensibility methods, such as for validation logic in OnValidate() and the like.

I decided to use interfaces to make my objects uniform across projects. These interfaces must be able to be reused on any number of projects.

I decided to generate partial classes to avoid an inheritance chain.

I decided to not hand-edit generated code ever.

I decided code generation MUST be idempotent always.

I decided to use a single-column, uniqueidentifier (Guid), business-layer-generated, primary key in every table. It makes sense. It is easy. It is simple. It is strong. Etc. I can use a unique key for other lookup types, multi-column association tables, natural keys, etc.

Solution —

As above, I used T4, T4 ToolBox, Linq To Sql Entity Base, Repository design pattern, and a few other goodies.

For the CodeTable template, note that the output class may be used in many ways. For example, to make a call to the built-in method this.User.IsInRole(string targetRoleName), such as to enforce the business rule that says “hide this button if the user is not in the admin role”, then one must have somewhere hardcoded that targetRoleName. Generally, one either hard-codes the value somewhere or do this one does other odd tricks, each of which is manually maintained. Objection 1: If the code table value gets deleted or changed then you have to recompile. Answer 1: Well, what better way is there to solve the issue of calling IsUserInRole() with a string role name? This is not for everyone or everything. The hand-hardcode would break too and this is easier to maintain. Also, this risk can be mitigated by a controlled admin workflow and other code. Also, this is intended for semi-static, system-level values.

Interface —

This is just a taste of the code and a version as of about 2009-Dec-14 and a more-recent version probably exists at the Download Link. This is the interface. It may change. It seems is more-or-less done right now, I think. Please note that if this interface does change, then I might not update this in-line code sample in this post. However, the code download link above will have the latest version. The following is just to give the reader an idea of the interface.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;

namespace Team.Framework.Interfaces.BusinessLayer.BusinessEntities
{
	/// <summary>
	/// This is a shared manager interface for projects using Linq To Sql (AKA L2s).
	/// </summary>
	/// <remarks>
	/// Note that implementation is at discretion of the architect; but, recommendations are noted below.
	/// </remarks>
	public interface IL2sEntityManager
	{
		/// <summary>
		/// This saves a new entity.
		/// </summary>
		/// <param name="targetEntity">This is the entity to be saved.</param>
		/// <remarks>
		/// Note that the implementation must cast to the appropriate type.
		/// </remarks>
		void Create(object targetEntity);

		/// <summary>
		/// This deletes an existing entity if the ID does exist.
		/// </summary>
		/// <param name="targetPkId">This</param>
		/// <remarks>
		/// Note that it is recommended that this method should gracefully do nothing if the ID does not exist.
		/// Note that the ID is of type "object" in order to support all key types, such as Guid and Long.
		/// </remarks>
		void DeleteByPkId(object targetPkId);

		/// <summary>
		/// This deletes an existing entity if the ID does exist, for each in the given list.
		/// </summary>
		/// <param name="targetPkIdList">This is the list of ID values to delete.</param>
		/// <remarks>
		/// Note that it is recommended that this method should gracefully do nothing if the ID does not exist.
		/// Note that the ID is of type "ArrayList" in order to support all key types, such as Guid and Long.
		/// </remarks>
		void DeleteByPkId(ArrayList targetPkIdList);

		/// <summary>
		/// This returns the total count.
		/// </summary>
		/// <returns>This is the count or zero if there are no rows.</returns>
		long GetCount();

		/// <summary>
		/// This will instantiate and return a new, non-null, unsaved, empty object using the object's default constructor.
		/// </summary>
		/// <returns>This is the new object.</returns>
		/// <remarks>
		/// Note that the callsite must cast to the appropriate type.
		/// </remarks>
		object Instantiate();

		/// <summary>
		/// This returns a flag that indicates object existence.
		/// </summary>
		/// <param name="targetPkId">This is the ID to check.</param>
		/// <returns>This is "true" if the ID does exist, otherwise this is "false".</returns>
		bool IsExistingPkId(object targetPkId);

		/// <summary>
		/// This returns all entities or it returns null if none are found.
		/// </summary>
		/// <returns>This is the set of entities.</returns>
		/// <remarks>
		/// Note that the callsite must cast to the appropriate type.
		/// </remarks>
		System.Linq.IQueryable RetrieveAll();

		/// <summary>
		/// This returns an entity if ID does exists otherwise it returns null.
		/// </summary>
		/// <param name="targetPkId">This is the ID to check.</param>
		/// <returns>This is the entity if the ID does exists or null if the ID does not exist.</returns>
		/// <remarks>
		/// Note that the callsite must cast to the appropriate type.
		/// </remarks>
		object RetrieveByPkId(object targetPkId);

		/// <summary>
		/// This saves an existing entity.
		/// </summary>
		/// <param name="targetEntity">This is the entity to save.</param>
		void Update(object targetEntity);
	}
}

Closing —

 
I am going to stop writing there for now. I have no more time at the moment. I admit, it is all far from perfect, but it is a start.

Comments, criticism, etc, are welcome– just no swearing please.

🙂

HTH.

Thank you.

— Mark Kamoski

Advertisements

Author: mkamoski1

n/a

5 thoughts on “T4, T4 ToolBox, Linq To Sql Entity Base, Code Description”

  1. Is the use of “object” as argument type for CRUD methods intentional? Would it be possible/desirable to use generic interfaces here to make them stronly-typed?

    1. Well. The goal is to have an interface that works for all entities on all projects. Since some use BigInt and some use UniqueIdentifier and Etc, object works with all. The API is the same for all and the generated internals cast as necessary. It works as GEFN so far and I can cast to the interface IL2sEntity when I like. I could stongly type my method signature in the codegen but I would not have a common interface. Etc.

      Our team is very small. This works so far. But I will improve it if I can.

      Could I do it with generics? I do not know and if I can then I do not know how. As such my design is Q and D but I am long overdue to learn generics beyond the tip. Maybe I will rewrite if I have time.

      If you have one air code method signature to replace RetrieveByPkId(object) then please do share it.

    2. Oleg —

      I think that I can write something like this…

      public T Retrieve(T targetPkId)

      …which was suggested to me here…

      http://linq2sqleb.codeplex.com/Thread/View.aspx?ThreadId=69122&ANCHOR#Post236402

      …I may just do that.

      Is that along the lines of what you would suggest?

      What do you think?

      Note that one hesitation that I have is that I do not REALLY understand that syntax so if I use that code and need to debug it then it will be tricky.

      It seems to me that all of this points to the central problem at hand, namely my lack of knowledge regarding generics.

      Thank you.

      — Mark Kamoski

Comments are closed.