Byte Commander
Byte Commander

Reputation: 6736

Need help simplifying methods using generics in C#

I have functions like this one below that loads a database table using LinqToSql and returns a list of the entries converted into my custom model class:

    public List<AreaModel> LoadListOfAreaModels(Boolean includeDeleted = false)
    {
        using (LinqToSqlDataContext dc = new LinqToSqlDataContext())
        {
            IQueryable<Areas> filtered = includeDeleted
                ? dc.Areas.Where((c) => !c.DataRowDeleted)
                : dc.Areas;
            return filtered.Select((c) => AreaModel.ModelFactoryFromLinq(c)).ToList();
        }
    }

Now I have quite a few tables and all have to get loaded and converted in the same way.

Can I somehow avoid typing this function a dozen times and just changing the AreaModel and Areas to e.g. TownModel and Towns by using a generic function?

I already tried this, but failed because I could not find a way to call a static class method if I only have the generic type parameter of it.

Further info: All XxxModel classes have the common superclass ModelBase (which does not contain the static factory method I need to call though!), all Linq table classes (like Areas or Towns) implement the common interface ILinqClass.

Any ideas on how to implement this the most elegant way?


Inspired by knittl's answer, I came up with this solution:

    public List<TModel> LoadListOfModels<TLinq, TModel>(
        Func<TLinq, bool> filter,
        Func<TLinq, TModel> modelFactory
        )
        where TLinq : class, ILinqClass
        where TModel : ModelBase
    {
        using (LinqToSqlDataContext dc = new LinqToSqlDataContext())
        {
            return dc.GetTable<TLinq>()
                .Where(filter)
                .Select(modelFactory)
                .ToList();
        }
    }

Upvotes: 1

Views: 109

Answers (3)

Slava Utesinov
Slava Utesinov

Reputation: 13488

Try this approach:

public abstract class BaseClass<TModel> where TModel : class
{
    public bool DataRowDeleted { get; set; }
    public abstract TModel ModelFactoryFromLinq();
}

public class Area : BaseClass<AreaModel>
{
    public override AreaModel ModelFactoryFromLinq()
    {
        return new AreaModel();
    }
}

public static List<TModel> LoadListOfAreaModels<TContext, TModel>(bool includeDeleted = false)
    where TContext : BaseClass<TModel>, new()
    where TModel : class
{
    using (var dc = new LinqToSqlDataContext())
    {
        IQueryable<TContext> filtered = includeDeleted
            ? dc.GetTable<TContext>().Where((c) => !c.DataRowDeleted)
            : dc.GetTable<TContext>();
        return filtered.Select((c) => c.ModelFactoryFromLinq()).ToList();
    }
}

IMPLEMENTATION:

List<AreaModel> result = SomeClassName.LoadListOfAreaModels<Area, AreaModel>();

Upvotes: 1

knittl
knittl

Reputation: 265211

You should be able to pass delegates/Funcs:

public List<TModel> LoadListOfModels<TDbModel, TModel>(
  Func<LinqToSqlDataContext, TDbModel> modelSelector,
  Func<TDbModel, bool> isDeleted,
  Func<TDbModel, TModel> modelFactory,
  bool includeDeleted = false)
{
    using (LinqToSqlDataContext dc = new LinqToSqlDataContext())
    {
        var models = modelSelector(dc);
        var filtered = includeDeleted
            ? models.Where(c => !isDeleted(c))
            : models;

        return filtered.Select(modelFactory).ToList();
    }
}

For Area call as follows:

 LoadListOfModels(
   dc => dc.Areas,
   c => c.DataRowDeleted,
   Area.ModelFactoryFromLinq,
   includeDeleted);

For Town:

 LoadListOfModels(
   dc => dc.Towns,
   c => c.DataRowDeleted,
   Town.ModelFactoryFromLinq,
   includeDeleted);

You might even be able to get rid of the includeDeleted parameter. Simply pass a filter func Func<TDbModel, bool> filter.

return modelsSelector(dc)
  .Where(filter)
  .Select(modelFactory)
  .ToList();

Call:

LoadListOfModels(dc => dc.Areas, c => true, Area.ModelFactoryFromLinq);
LoadListOfModels(dc => dc.Areas, c => !c.DataRowDeleted, Area.ModelFactoryFromLinq);

Upvotes: 2

Lee
Lee

Reputation: 144136

If DataRowDeleted is defined in ModelBase you should be able to do:

public static IQueryable<TModel> LoadListOfQuery<T, TModel>(IQueryable<T> source, Expression<Func<T, TModel>> selector, bool includeDeleted = false) where T : ModelBase {
    IQueryable<T> filtered = includeDeleted ? source : source.Where(s => !s.DataRowDeleted);
    return filtered.Select(selector);
}

Then add a function to evaluate a query given a context:

public static List<T> ExecList(Func<LinqToSqlDataContext, IQueryable<T>> qf)
{
    using(var c = new LinqToSqlDataContext())
    {
         var q = qf(c);
         return q.ToList();
    }
}

Then you can do:

public List<AreaModel> LoadListOfAreaModels(bool includeDeleted = false)
{
    ExecList(c => LoadListOfQuery(c.Areas, a => AreaModel.ModelFactoryFromLinq(a), includeDeleted);
}

Upvotes: 0

Related Questions