2
\$\begingroup\$

A little background - these are classes I've written on top of an Entity Framework model that represents tables on Sql Server with the following names:

  • Dictionary.Databases, Dictionary.Schemas, Dictionary.Tables, Dictionary.Columns
  • SysData.Databases, SysData.Schemas, SysData.Tables, SysData.Columns
  • Compare.Databases, Compare.Schemas, Compare.Tables, Compare.Columns

Each 'set' of tables (eg *.Databases) are roughly identical - all of them contain relevant primary key fields (eg all the Schemas tables contain DatabaseID and SchemaID).

Tables in the Dictionary and SysData schemas contain metadata fields extracted from the sys tables (eg the Columns tables contain ColumnName, ColumnType, IsNullable etc).

The tables in the Dictionary schema also contain additional common fields for custom user-entered data (Description, etc).

I've noticed that the code I've just written for 4 classes looks very similar, and I feel like I should be able to write them better and apply some structure. They don't quite fit anything I know how to do with interfaces, however:

// for brevity's sake, only listing the very simple functions
// in the controllers here, if anyone's wondering why I'm
// bothering writing these classes instead of just going straight
// to my Entity Framework model when I need to, since these are all
// one-line methods

public class DictionaryDatabaseController {
    private DictionaryContext context;

    public DictionaryDatabaseController() {
        this.context = new DictionaryContext();
        // other initialization
    }

    public DictionaryDatabase FindDatabase(params object[] primaryKey) {
        return this.context.Databases.Find(primaryKey);
    }

    public DbSet<DictionaryDatabase> GetDatabases() {
        return this.context.Databases;
    }

    // ... etc ...
}

// This class is really similar to the one above...
public class SysDataDatabaseController {
    // also has a private context, just a different type.
    // SysDataContext and DictionaryContext both extend DbContext
    private SysDataContext context;

    public SysDataDatabaseController() {
        this.context = new SysDataContext();
        // other initialization
    }

    // Identical method, different but related return type.
    // DictionaryDatabase and SysDataDatabase both implement IDatabase
    public SysDataDatabase FindDatabase(params object[] primaryKey) {
        return this.context.Databases.Find(primaryKey);
    }

    // again, basically the same, but returning a `DbSet` of
    // `SysDataDatabases` rather than `DictionaryDatabase`s
    public DbSet<SysDataDatabase> GetDatabases() {
        return this.context.Databases;
    }

    // ... etc ...
}

// another class called CompareDatabaseController. Same stuff inside,
// but with CompareContext and CompareDatabase

See the commonality? I feel like I should be able to define an interface called IDatabaseController, but the return type of each function & the type of the context private member is different.

The two contexts are structurally related - they both extend an Entity Framework class called DbContext.

Similarly DictionaryDatabase, SysDataDatabase and CompareDatabase all implement an interface called IDatabase

You can't, however, define an interface like this and make those classes implement it:

interface IDatabaseController {
    IDatabase FindDatabase(params object[] primaryKey);
    DbSet<IDatabase> GetDatabases();
    // ... other common functions ...
}

As the return type must be identical to that defined in the interface (eg, DictionaryDatabaseController.FindDatabase would have to return IDatabase rather than DictionaryDatabase). But I feel like I should be able to do something like this, and can't figure out what. It feels like something that's probably been commonly encountered before. Maybe there's a design pattern for it.

Making me further convinced that I can and should restructure this, there are 3 other sets of classes for Schemas, Tables, and Columns (DictionarySchemaController, SysDataSchemaController, etc etc), which again all look suspiciously similar to the above.

Any suggestions on how to improve this?


One thing I forgot to note - all the Context classes provided by Entity Framework are again all very similar and also feel like can be put into some kind of structure:

public class DictionaryContext {
    public DbSet<DictionaryDatabase> Databases { get; set; }
    public DbSet<DictionarySchema> Schemas { get; set; }
    public DbSet<DictionaryTable> Tables { get; set; }
    public DbSet<DictionaryColumn> Columns { get; set; }
    // ... other stuff ...
}

public class SysDataContext {
    public DbSet<SysDataDatabase> Databases { get; set; }
    public DbSet<SysDataSchema> Schemas { get; set; }
    public DbSet<SysDataTable> Tables { get; set; }
    public DbSet<SysDataColumn> Columns { get; set; }
    // ... other stuff ...
}

public class CompareContext {
    public DbSet<CompareDatabase> Databases { get; set; }
    public DbSet<CompareSchema> Schemas { get; set; }
    public DbSet<CompareTable> Tables { get; set; }
    public DbSet<CompareColumn> Columns { get; set; }
    // ... other stuff ...
}
\$\endgroup\$
1
  • \$\begingroup\$ Wasn't sure if this was fully suitable for CodeReview, as the code definitely works as-is, but I'm asking for stuctural stuff rather than providing my own and asking for it to be reviewed. Happy to move it if it's a better fit on a different SE. \$\endgroup\$
    – Kai
    Jun 24, 2015 at 11:34

1 Answer 1

3
\$\begingroup\$

If you inject the context instead of "newing it up" in the constructor you should be able to solve this. An interface may not be entirely appropriate, but a generic base class would be.

public class DatabaseController<TDatabase, TContext> 
                                where TDatabase : IDatabase
                                where TContext : IContext 
{
    private TContext context;

    public DatabaseController(TContext context) 
    {
        this.context = context;
        // other initialization
    }

    public TDatabase FindDatabase(params object[] primaryKey)
    {
        return this.context.Databases.Find(primaryKey);
    }

    public DbSet<TDatabase> GetDatabases() 
    {
        return this.context.Databases;
    }

    // ... etc ...
}

Using a filter allows you to access the interface members. Of course, that means you'd have to go mucking around with the auto-generated EntityFramework classes making them all implement a common interface though. So, all in all, I'm not sure I'd recommend it. Boilerplate code is the price we pay for working with many frameworks.

\$\endgroup\$
4
  • \$\begingroup\$ How would TContext 'know' that it's a DbContext and has a Databases member? Completely forgot this problem when writing the question (I'll edit it in), but again a similar problem where all these Context classes have Databases, Schemas, Tables & Columns properties but are different types. I think the injection method might work there too? \$\endgroup\$
    – Kai
    Jun 24, 2015 at 12:24
  • \$\begingroup\$ I'm not 100% certain, but what if you use a filter on TContext that ensures it implements a specific interface that exposes those common members? \$\endgroup\$
    – RubberDuck
    Jun 24, 2015 at 12:31
  • \$\begingroup\$ It's that 'specific interface' for the context that's the problem there haha. See my edit to the question - same property name, different types. Can't do it with an interface (I think) \$\endgroup\$
    – Kai
    Jun 24, 2015 at 12:58
  • \$\begingroup\$ I saw that and updated my answer. \$\endgroup\$
    – RubberDuck
    Jun 24, 2015 at 12:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.