views:

165

answers:

3

I'm working on a huge, old project with a lot of brittle code, some of which has been around since the .NET 1.0 era, and it has been and will be worked on by other people... so I'd like to change as little as possible.

I have one project in my solution that contains DataSet.xsd. This project compiles to a separate assembly (Data.dll). The database schema includes several tables arranged more or less hierarchically, but the only way the tables are actually linked together is through joins. I can get, e.g. DepartmentRow and EmployeeRow objects from the autogenerated code. EmployeeRow contains information from the employee's corresponding DepartmentRow through a join.

I'm making a new report to view multiple departments and all their employees. If I use the existing data access scheme, all I will be able to get is a spreadsheet-like output where each employee is represented on one line, with department information repeated over and over in its appropriate columns. E.g.:

Department1...Employee1...
Department1...Employee2...
Department2...Employee3...

But what the customer would like is to have each department render like a heading, with a list of employees beneath each. E.g.:

- Department1...
      Employee1...
      Employee2...
+ Department2...

I'm trying to do this by inheriting hierarchical objects from the autogenerated Row objects. E.g.:

public class Department : DataSet.DepartmentRow {
    public List<Employee> Employees;
}

That way I could nest the data in the report by using a collection of Department objects as the DataSource, each of which will put its list of Employees in a subreport.

The problem is that this gives me a The type Data.DataSet.DepartmentRow has no constructors defined error. And when I try to make a constructor, e.g.

public class Department : DataSet.DepartmentRow {
    private Department() { }
    public List<Employee> Employees;
}

I get a 'Data.DataSet.DepartmentRow(System.Data.DataRowBuilder)' is inaccessible due to its protection level. error in addition to the first one.

Is there a way to accomplish what I'm trying to do? Or is there something else I should be trying entirely?

+3  A: 

I was able to inherit from a data row in a quick test. Note that this is in the same assembly as the data row class. The data row class's constructor is marked as 'internal'.

using System;
using System.Data;

namespace Blah
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    public class Class1 : MyDataSet.MyTableRow
    {
        public Class1(DataRowBuilder rb) : base(rb)
        {
            //
            // TODO: Add constructor logic here
            //
        }
    }
}
chilltemp
I've tested this as far as building the project. I still wouldn't do this in my own code, even though I understand why you want to.
chilltemp
Did you try it with the DataSet in a different assembly? I tried that on my example and got the "inaccessible due to its protection level" error.
Calvin Fisher
You can't do this in a different assembly without modifying the accessors in the original code.
chilltemp
Theoretically, you could do this via reflection by creating a runtime type. I'm not recommending this, as it could easily become a maintenance/debugging nightmare. Just search SO for "reflection create runtime type" if you really want to dive into that rabbit hole.
chilltemp
It's ugly, but you could do this in a different assembly by adding the [`InternalsVisibleTo`](http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx) attribute to the strongly-typed `DataRow` class and specifying that the new assembly can see it.
Jeff Sternal
+2  A: 

I am not sure what the assembly layout is. If your new inheriting Type resides outside "Data.dll" and you need to access the internal constructor... Assuming you can still change this assembly, you could try adding the "InternalsVisibleToAttribute" ( http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx ). Hope this helps.

JoeGeeky
A: 

I eventually figured out a workaround that has the following benefits:

  1. I don't have to add a friend to the Data assembly; I only have to modify one file in the part of the assembly where I normally make modifications.
  2. I don't have to change auto-generated code which will be overwritten as soon as it is re-generated

In the code-behind file for the dataset (DataSet.cs, acessed in VS2008 by expanding DataSet.xsd), I added a new partial class definition that adds a public constructor with a different signature but which is functionally identical to the existing internal constructor:

public partial class DataSet {
    public partial class DepartmentRow {
        public DepartmentRow(global::System.Data.DataRowBuilder rb, string discard) : this(rb) { }
    }
    public partial class EmployeeRow {
        public EmployeeRow(global::System.Data.DataRowBuilder rb, string discard) : this(rb) { }
    }
}

Then, I can use this constructor in my derived class:

public class Department : DataSet.DepartmentRow {
    public Department(global::System.Data.DataRowBuilder DataRowBuilder rb) : base(rb, "discard") { }

    public List<Employee> Employees;

    public string[] SomeFrequentlyUsedGroupOfFields {
        get {
            return new string[] { this.OneField, this.AnotherField };
        }
    }

    public bool CanUserSeeDepartmentInformation(int UserID) { }
}

And voila!

Unfortunately, after achieving this result, I realized that it's not much use, because the auto-generated code in DataSetTableAdapters.DepartmentTableAdapter still returns objects of type DataSet.DepartmentRow, and I would have to downcast to get my Department object with the extra properties/methods. This is not allowed, and I can't see an easy way around that fact.

I could just move all of my new members to the partial class code-behind, but I'm not sure what architecture specs that would violate. (There seems to be some arbitrary application of the distinction between data logic and business logic.)

Calvin Fisher