views:

507

answers:

2

Hi guys,

I've got a nice DAL generated using Subsonic. Is there a way I can generate a skeleton for the BLL? I don't want to plug the SS layer directly into my GUI.

I've trudged through both SS forums and it seems that everyone calls the SSS-generated layer a DAL, but they use it as a BLL.

Have you used SS and seperated the DAL and BLL tiers without manually coding the BLL from scratch?

+1  A: 

Yes I use seperate DAL and BLL except when the project is really small and there is not much of the business logic.

You are right doing all the implementation in BAL for the DAL properties is really tiresome and defeats the code generation goodness of SS. I've created a little console app that goes through DAL and churn out skeleton BLL's:

Here is the code of it. Please remember it is very crude one (I'm pasting it here in hope of getting it scrutinized by SO fellows and improve on it):

class Program
{
    static void Main(string[] args)
    {
        InitTypesDictionary();
        ProcessFile("c:\\temp\\myBll", "YOUR_BLL_NAMESPACE");//args[0], args[1]);

        Console.ReadLine();
    }

    private static void InitTypesDictionary()
    {
        typesMap = new Dictionary<string, string>();
        typesMap.Add("System.String", "string");
        typesMap.Add("System.Int32", "int");
        typesMap.Add("System.Decimal", "decimal");
        typesMap.Add("System.Double", "double");
        typesMap.Add("System.Guid", "Guid");
        typesMap.Add("System.DateTime", "DateTime");
        typesMap.Add("System.Boolean", "bool");
        typesMap.Add("System.Byte", "byte");
        typesMap.Add("System.Short", "short");
        typesMap.Add("System.Nullable`1[System.Int32]", "int?");
        typesMap.Add("System.Nullable`1[System.DateTime]", "DateTime?");
        typesMap.Add("System.Nullable`1[System.Decimal]", "decimal?");
        typesMap.Add("System.Nullable`1[System.Double]", "double?");
        typesMap.Add("System.Nullable`1[System.Boolean]", "bool?");
    }

    private static void WriteError(string msg)
    {
        WriteInfo(msg, ConsoleColor.Red);
    }

    private static void WriteTypeName(string name)
    {
        WriteInfo(name, ConsoleColor.Blue);
    }

    private static void WriteInfo(string info, ConsoleColor cc)
    {
        ConsoleColor clr = Console.ForegroundColor;
        Console.ForegroundColor = cc;
        Console.WriteLine(info);
        Console.ForegroundColor = clr;
    }

    private static void ProcessFile(string savePath, string _namespace)
    {
        Assembly asm = Assembly.GetAssembly(typeof(ROMS.DAL.RomsAdBusiness));
        //Assembly asm = Assembly.ReflectionOnlyLoad("ROMS.DAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");

        Type[] types = asm.GetTypes();

        foreach (Type t in types)
        {
            if (t.BaseType.Name.Contains("ActiveRecord"))
            {
                WriteTypeName("Processing " + t.Name);
                ProcessType(t, savePath, _namespace);
            }
        }
    }

    private static void ProcessType(Type t, string path, string nsp)
    {
        string className = t.Name.Substring(4);

        StringBuilder sbCode = new StringBuilder();

        sbCode.Append(imports);
        //sbCode.Append(Environment.NewLine);

        sbCode.AppendFormat(comments, t.Name, DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"));

        sbCode.AppendFormat(namespaceStart, nsp);
        //sbCode.Append(Environment.NewLine);

        sbCode.AppendFormat(_class, className);
        //sbCode.Append(Environment.NewLine);

        sbCode.AppendFormat(dalObject, t.Name);

        sbCode.AppendFormat(regionStart, "ctor");
        //sbCode.Append(Environment.NewLine);

        sbCode.AppendFormat(ctorString, className, t.Name);

        sbCode.AppendFormat(ctorStringPK, className, t.Name);

        sbCode.AppendFormat(ctorStringObject, className, t.Name);

        sbCode.AppendFormat(regionEnd, "");
        //sbCode.Append(Environment.NewLine);

        PropertyInfo[] properties = t.GetProperties();

        //if (properties.Count() > 0)
        //{
        sbCode.AppendFormat(regionStart, "Properties");
        //sbCode.Append(Environment.NewLine);
        //}

        foreach (PropertyInfo p in properties)
        {
            if (SkipProperties.Contains(p.Name)) continue;

            if (p.Name == className)
                sbCode.AppendFormat(propertyValue, GetPropertyTypeName(p.PropertyType.ToString()), p.Name + "Value", p.Name);
            else if (p.Name.ToLower().Contains("roms"))
                sbCode.AppendFormat(propertyInternal, GetPropertyTypeName(p.PropertyType.ToString()), p.Name);
            else
                sbCode.AppendFormat(property, GetPropertyTypeName(p.PropertyType.ToString()), p.Name);

            //sbCode.Append(Environment.NewLine);
        }

        sbCode.Append(isNew_Validate_Properties);


        //if (properties.Count() > 0)
        //{
        sbCode.AppendFormat(regionEnd, "");
        //sbCode.Append(Environment.NewLine);
        //}

        sbCode.AppendFormat(regionStart, "methods");
        sbCode.Append(saveMethod);
        sbCode.Append(deleteMethod.Replace("_CLASSNAME_", className));
        sbCode.AppendFormat(regionEnd, "");

        //Add Fetch as Collection Methods
        sbCode.Append(getCollectionsMethods.Replace("_CLASSNAME_", className).Replace("_DOUBLEQUOTE_", "\""));

        //Define Columns Structure
        Type cols = t.GetNestedType("Columns");
        if (cols != null)
        {
            StringBuilder sbCols = new StringBuilder(columnsStructStart);
            MemberInfo[] fields = cols.GetMembers();
            foreach (MemberInfo mi in fields)
            {
                if (mi.MemberType == MemberTypes.Field)
                    sbCols.AppendFormat(columnDeclaration, mi.Name);
            }
            sbCols.Append(columnsStructEnd);
            sbCode.Append(sbCols.ToString());
        }

        sbCode.Append("}_NL_}_NL_");
        var fileName = WriteFile(path, nsp, className, sbCode);
        WriteInfo("Written file: " + fileName, ConsoleColor.Yellow);
    }

    private static string GetPropertyTypeName(string s)
    {
        if (typesMap.ContainsKey(s)) return typesMap[s];

        if (s.Contains("Nullable`"))
        {
            s = s.Substring(s.IndexOf("[") + 1);
            s = s.Substring(0, s.IndexOf("]"));

            if (typesMap.ContainsKey(s))
                return typesMap[s] + "?";
            else
                return "Nullable<" + s + ">";
        }

        if (s.StartsWith("System."))
            return s.Substring(7);

        if (s.LastIndexOf(".") > 0)
            return s.Substring(s.LastIndexOf(".") + 1);

        return s;
    }

    private static string WriteFile(string path, string nsp, string typeName, StringBuilder sbCode)
    {
        string filename = GetFilePath(path, nsp, typeName);
        TextWriter tw = new StreamWriter(filename);
        StringBuilder sb = sbCode.Replace("_BS_", "{")
            .Replace("_BE_", "}")
            .Replace("_NL_", Environment.NewLine)
            .Replace("_DOUBLEQUOTE_", "\"");
        tw.Write(sb.ToString());
        tw.Flush();
        tw.Close();
        return filename;
    }

    private static string GetFilePath(string path, string nsp, string typeName)
    {
        path = path.EndsWith("\\") ? path : path + "\\";
        path += typeName + ".cs";
        return path;
    }

    static bool IsNullableType(Type theType)
    {
        return (theType.IsGenericType && theType.
          GetGenericTypeDefinition().Equals
          (typeof(Nullable<>)));
    }

    private static string[] SkipProperties = new[]{"IsLoaded", "IsNew", "IsDirty",
        "TableName", "ProviderName",         
    "NullExceptionMessage","InvalidTypeExceptionMessage",
        "LengthExceptionMessage",         
    "AuditId","Schema","ValidateWhenSaving","DirtyColumns","Errors"};

    static IDictionary<string, string> typesMap;

    static string comments = @"_NL_///<sumary>
    ///This class uses {0} from YOUR_DAL_NAMESPACE
    ///Created by MyCodeGen (YOUR_NAME) on {1}
    ///</sumary>_NL_";
    static string dalObject = "_NL_private YOUR_DAL_NAMESPACE.{0} _dalObject;_NL_";
    static string namespaceStart = "namespace {0} _NL_ _BS_ _NL_";
    static string property = "public {0} {1} _NL_ _BS_  _NL_ get _BS_  return         
    _dalObject.{1}; _BE_ _NL_ " +
        "set  _BS_  _dalObject.{1} = value; _BE_  _NL_ _BE_ _NL_";
    static string propertyInternal = "internal {0} {1} _NL_ _BS_  _NL_ get _BS_  return 
    _dalObject.{1}; _BE_ _NL_ " +
        "set  _BS_  _dalObject.{1} = value; _BE_  _NL_ _BE_ _NL_";
    static string propertyValue = "public {0} {1} _NL_ _BS_  _NL_ get _BS_  return 
    _dalObject.{2}; _BE_ _NL_ " +
                "set  _BS_  _dalObject.{2} = value; _BE_  _NL_ _BE_ _NL_";
    static string _class = "public partial class  {0}  _NL_ _BS_ ";
    static string regionStart = "#region  {0} _NL_";
    static string regionEnd = "#endregion{0}_NL__NL_";
    static string ctorString = "[DebuggerStepThrough]_NL_public {0}() _NL_ 
    _BS__NL__dalObject = new YOUR_DAL_NAMESPACE.{1}(); _NL_ _BE_ _NL_";
    static string ctorStringPK = "[DebuggerStepThrough]_NL_public {0}(int pk) _NL_ 
    _BS__NL__dalObject = new YOUR_DAL_NAMESPACE.{1}(pk);_NL__BE__NL_";
    static string ctorStringObject = "[DebuggerStepThrough]_NL_public 
    {0}(YOUR_DAL_NAMESPACE.{1} dalObject) _NL_ _BS__NL__dalObject = dalObject; _NL_ 
    if(_dalObject==null) _NL__dalObject = new YOUR_DAL_NAMESPACE.{1}();_BE_ _NL_";

    static string columnsStructStart = @"_NL_#region Columns Struct
    public struct Columns
    {
   ";
    static string columnsStructEnd=@"
}
#endregion_NL_";
    static string columnDeclaration = "public static string         
    {0}=_DOUBLEQUOTE_{0}_DOUBLEQUOTE_;_NL_";
    static string saveMethod = "_NL_public bool Save() _NL__BS__NL_bool ret=IsValid;         
    _NL_ if(ret)_NL__dalObject.Save(); _NL_ return ret;_NL__BE__NL_";

    static string deleteMethod = @"public int Delete()
    {
        string pkColumn=_dalObject.GetSchema().PrimaryKey.ColumnName;
        object pkValue = _dalObject.GetColumnValue(pkColumn);

        return ActiveRecord<Roms_CLASSNAME_>.Delete(pkValue);
    }
    ";

    static string isNew_Validate_Properties = @"/// <summary>
    /// Enquiries underlying database object to know if it is persisted in 
    ///database or not.
    /// True if object has never been saved to database, false otherwise
    /// </summary>
    public bool IsNew
    _BS_
        get _BS_ return _dalObject.IsNew; _BE_
    _BE_

    /// <summary>
    /// Validates the underlying dataobject for the lengeth, range of the 
    ///columns defined
    /// in database. Should be called before pushing object to database 
    ///(before saving or updating).
    /// </summary>
    public bool IsValid
    _BS_
        get _BS_ return _dalObject.Validate(); _BE_
    _BE_

    /// <summary>
    /// This string of validation error messages (&lt;br/&gt; seperated)
    /// if the object is not valid.
    /// </summary>
    /// <returns>string</returns>
    public string GetErrors
    _BS_
        get
        _BS_
            StringBuilder sb=new StringBuilder();
            foreach (var v in _dalObject.GetErrors())
                sb.AppendFormat(_DOUBLEQUOTE__BS_0_BE_<br/>_DOUBLEQUOTE_, v);
            return sb.ToString();
        _BE_
    _BE__NL_";

    static string imports =
    @"using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data;
    using System.Diagnostics;
    using System.Reflection;
    using SubSonic;
    using YOUR_DAL_NAMESPACE;
    ";

    static string getCollectionsMethods = @"#region Fetch as Collection Methods
    ///<sumary>
    ///Returns the collection containing objects of type _CLASSNAME_ corresponding to         
    _CLASSNAME_Collection
    ///objects passed
    ///</sumary>
    [DebuggerStepThrough]
    public static IList<_CLASSNAME_> _CLASSNAME_s(Roms_CLASSNAME_Collection coll)
    {
        IList<_CLASSNAME_> list=new List<_CLASSNAME_>();

        foreach(var roprof in coll)
            list.Add(new _CLASSNAME_(roprof));

        return list;
    }        

    /// <summary>
    /// Returns the IList&lt;_CLASSNAME_&gt; of _CLASSNAME_ objects 
    /// </summary>
    /// <returns>IList&lt;_CLASSNAME_&gt;</returns>
    [DebuggerStepThrough]
    public static IList<_CLASSNAME_> _CLASSNAME_s()
    {
        return _CLASSNAME_s(_DOUBLEQUOTE__DOUBLEQUOTE_,null);
    }

    /// <summary>
    /// Returns the IList&lt;_CLASSNAME_&gt; of _CLASSNAME_ objects having
    /// value of <see cref=_DOUBLEQUOTE_columnName_DOUBLEQUOTE_/> = 
    ///<see cref=_DOUBLEQUOTE_value_DOUBLEQUOTE_/>
    /// </summary>
    /// <param name=_DOUBLEQUOTE_columnName_DOUBLEQUOTE_>Name of the column</param>
    /// <param name=_DOUBLEQUOTE_value_DOUBLEQUOTE_>Value of the column</param>
    /// <returns>IList&lt;_CLASSNAME_&gt;</returns>
    [DebuggerStepThrough]
    public static IList<_CLASSNAME_> _CLASSNAME_s(string columnName, object value)
    {
        IList<_CLASSNAME_> collection = new List<_CLASSNAME_>();

        Roms_CLASSNAME_Collection coll = null;

        if (!string.IsNullOrEmpty(columnName))
        {
            Roms_CLASSNAME_ obj = new Roms_CLASSNAME_();

            columnName = obj.GetType().GetNestedType(
                    _DOUBLEQUOTE_Columns_DOUBLEQUOTE_).
            GetField(columnName).GetValue(obj).ToString();
        }

        if (!string.IsNullOrEmpty(columnName) && value != null)
        {
            coll = (new Roms_CLASSNAME_Collection()).Where(columnName, 
                    value).Load();
        }
        else
        {
            coll = (new Roms_CLASSNAME_Collection()).Load();
        }           

        if (coll != null)
            foreach (var v in coll)
                collection.Add(new _CLASSNAME_(v));

        return collection;
    }
#endregion
";

PS:- Please take care of YOUR_DAL_NAMESPACE, YOUR_BLL_NAMESPACE and YOUR_NAME placeholders if ever you try it.

TheVillageIdiot
+1  A: 

No, but there are some options. You can extend the generated table classes with partial class files to add more logic, and this might be good enough for many smaller applications. You also may need DTO classes as well, and subsonic 3's table classes usually seem to work as DTO objects. You can write additional t4 template files in subsonic 3 to create business classes, one class per table. The code will be a lot like the existing template code so should be pretty easy. You could even take the template code for the table classes in ss3 and use them in ss2 to generate files. It depends on how badly you want to generate a simple set of BLL classes.

P a u l
Thanks guys. I'll try to get SS to generate another template.