views:

310

answers:

2

While using SubSonic 3 with ActiveRecord T4 templates, the generated code shows many warnings about CLS-compliance, unused items, and lack of GetHashCode() implementation.

In order to avoid them, I did the following modifications:

// Structs.tt
[CLSCompliant(false)]                                    // added
public class <#=tbl.CleanName#>Table: DatabaseTable
{ ...

// ActiveRecord.tt
[CLSCompliant(false)]                                    // added
public partial class <#=tbl.ClassName#>: IActiveRecord
{
    #region Built-in testing
    #pragma warning disable 0169                         // added
    static IList<<#=tbl.ClassName#>> TestItems;
    #pragma warning restore 0169                         // added
    ...

    public override Int32 GetHashCode()                  // added
    {
      return this.KeyValue().GetHashCode();
    }

    ...

Is there a better way to get rid of the warnings? Or a better GetHashCode() implementation?

+2  A: 

Currently, the only way to get rid of the warnings is to update your t4 templates and submit a bug/fix to Rob. Or wait until somebody else does.

As for the GetHashCode implementation, I don't think you're going to find a good way to do this through templates. Hash code generation is very dependent on what state your object contains. And people with lots of letters after their name work long and hard to come up with hash code algorithms that are fast and return results with low chances of collision. Doing this from within a template that may generate a class with millions of different permutations of the state it may hold is a tall order to fill.

Probably the best thing Rob could have done would be to provide a default implementation that calls out to a partial method, checks the result and returns it if found. Here's an example:

public partial class Foo
{
    public override int GetHashCode()
    {
        int? result = null;
        TryGetHashCode(ref result);
        if (result.HasValue)
            return result.Value;
        return new Random().Next();
    }

    partial void TryGetHashCode(ref int? result);
}

public partial class Foo
{
    partial void TryGetHashCode(ref int? result)
    {
        result = 5;
    }
}

If you compile this without the implementation of TryGetHashCode, the compiler completely omits the call to TryGetHashCode and you go from the declaration of result to the check to see if it has value, which it never will, so the default implementation of the hash code is returned.

Will
+1 thank you for the code sample, I never tried before to use partial methods this way.
alexandrul
+1  A: 

I wanted a quick solution for this as well. The version that I am using does generate GetHashCode for tables that have a primary key that is a single int.

As our simple tables use text as their primary keys this didn't work out of the box. So I made the following change to the template near line 273 in ActiveRecord.tt

<#      if(tbl.PK.SysType=="int"){#>

        public override int GetHashCode() {
            return this.<#=tbl.PK.CleanName #>;
        }
<#      }#>
<#      else{#>
        public override int GetHashCode() {
            throw new NotImplementedException();
        }
<#      }#>

This way GetHashCode is generated for all the tables and stops the warnings, but will throw an exception if called (which we aren't).

We use this is for a testing application, not a website or anything like that, and this approach may not be valid for many situations.

Bruce McLeod
+1 I have an older application with only int/bigint primary keys, for which this nice solution might work just fine.
alexandrul