14

I have three classes, they all have a property Date. I would like to write a generic class to return all the records for one date. Now the problem is: how can I write the lambda expression using generic type T?

The code simple is as below (i'll not compile, because "r.Date" would not working,but it's the effect that I'd like to achive)

Class GenericService<T>: IGenericService<T> where T:class
{
      ...
      readonly IGenericRepository<T> _genericRepository;
      public IEnumerable<T> GetRecordList(DateTime date)
      {
             var query=_genericRepository.FindBy(r=>r.Date=date);
}

Thank you for you help!

Regards, Léona

4
  • Do they all have Date property on a common interface? If not, is that something you can add? Commented May 18, 2016 at 8:41
  • If there is no possibility for a common interfaceyou can use reflection Commented May 18, 2016 at 8:44
  • May be implement some interface and use it? Commented May 18, 2016 at 8:45
  • Except if you have complex algorithm to run on multiple tables, you might make your code too complex for no benefit. Given how generics works in C#, either use a common interface or Action<> and Func<>. Commented May 27, 2016 at 18:45

5 Answers 5

14

Write an interface which has IDate and all of your entities must implement IDate and write GenericService like this :

public class GenericService<T>: IGenericService<T> where T : class, IDate
{
    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    {
         var query=_genericRepository.FindBy(r => r.Date = date);
    }
}

public interface IDate
{
    DateTime Date{ set; get; }
}

public class Entity : IDate
{
    DateTime Date { set; get; }
}
Sign up to request clarification or add additional context in comments.

3 Comments

May I ask why you put a bounty? You don't like your own (upvoted and accepted) answer? What really are you expecting?
@IvanStoev the reason is simple as I set in my bounty This question has not received enough attention. and also I wanted to see other solution which what romain-aga also answered is interesting to me. and the final reason is I wanted to developers see this solution and consider using interface like this for generic useage
You'll probably want a second equals sign in that FindBy method call (unlike the OP). Either that or FindBy takes a Func<T, DateTime> and the func passed in will overwrite all date classes' Date property with the date searched for!
9
+50

Another solution if you can't implement an interface and don't want to use the reflection, could be to pass how to get the date to the GenericService:

By a property:

public class GenericService<T>: IGenericService<T> where T : class
{
    public Func<T, DateTime> DateGetter { get; set; }

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    {
        var query=_genericRepository.FindBy(r => DateGetter(r) == date);
    }
}

By the constructor:

public class GenericService<T>: IGenericService<T> where T : class
{
    Func<T, DateTime> _dateGetter;

    public GenericService(..., Func<T, DateTime> dateGetter)
    {
        _dateGetter = dateGetter
        ...
    }

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    {
        var query=_genericRepository.FindBy(r => _dateGetter(r) == date);
    }
}

By the method itself:

public class GenericService<T>: IGenericService<T> where T : class
{
    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date, Func<T, DateTime> dateGetter)
    {
        var query=_genericRepository.FindBy(r => dateGetter(r) == date);
    }
}

By type:

The Adam Ralph' solution inspired me this one (not the best I guess, but it still work)

public class GenericService<T>: IGenericService<T> where T : class
{
    public Dictionary<Type, Func<object, DateTime>> _dateGetter = new Dictionary<Type, Func<object, DateTime>>()
    {
        { typeof(TypeWithDate), x => ((TypeWithDate)x).Date },
        { typeof(TypeWithDate2), x => ((TypeWithDate2)x).Date }
    };

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    {
        var query=_genericRepository.FindBy(r => _dateGetter[typeof(T)](r) = date);
    }

}

8 Comments

Thanks for this solution. In fact, i could use in interface, but in fact, for the maintenance, it's better if I don't use it. I noticed in your solution, you also implemented IDate... I believe that is a mistake, isn't it? If yes, better to correct it... Thanks again :)
Yes it is a mistake, I copied pasted the code from the Arvin answer ^^"
thanks @romain-aga your solution is also a good way for this issue, but in this case if we are using EntityFramework we need LINQ to Entitie and I believe bu runnig this code this error will occur : The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. but again this is a valid solution if we are not using EntityFramework
I will have to make a test to check what you are saying. But I think if LINQ is an issue, you can still change all the LINQ queries by something more traditional (even if it's heavier to deal without it).
Aren't we missing another = in this: .FindBy(r => dateGetter(r) = date);?
|
8

Another option is to build an Expression dynamically. This can be useful if you don't want to or can't add an IDate interface to the model objects, and if FindBy needs an Expression<Func<T, bool>> (for example, it translates to IQueryable<T>.Where(...) internally for EntityFramework or similar.

Like @JonB's answer, this has no compile-time safety to ensure that T has a Date property or field. It will only fail at runtime.

public IEnumerable<T> GetRecordList(DateTime date)
{
    var parameterExpression = Expression.Parameter(typeof(T), "object");
    var propertyOrFieldExpression = Expression.PropertyOrField(parameterExpression, "Date");
    var equalityExpression = Expression.Equal(propertyOrFieldExpression, Expression.Constant(date, typeof(DateTime)));
    var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalityExpression, parameterExpression);

    var query = _genericRepository.FindBy(lambdaExpression);
    return query;
}

2 Comments

It's a better solution cause implementing IDate requires maintenance afterwards if more entities added.
In my scenario, the lambda query is going to be converted into a DB query, and it's unknown in coding phase that which properties of T are needed. This solution works for me. Brilliant!
3

Assuming the list of objects isn't enormous, writing something generic for 3 classes could be over engineering. KISS.

objects.Where(obj =>
    (obj as Foo)?.DateTime == date ||
    (obj as Bar)?.DateTime == date ||
    (obj as Baz)?.DateTime == date)

Comments

1

I'm not sure how your FindBy method is structured or what it returns, but you might be able to use the dynamic keyword like this:

   var query=_genericRepository.FindBy(r=>((dynamic)r).Date==date);

If the FindBy method is returning an IEnumerable<T>, then you might have to add a .Cast<T>() to the return statement otherwise you might get a type of IEnumerable<dynamic>.

This is by no means typesafe and you can get runtime errors if T doesn't have a Date property. But it's one option to consider.

1 Comment

Hello @JonB, Thanks for your answer. 1/ The FindBy method return an IQueryable<T>. 2/ Your code get a compile error: An expression tree may not contain a dynamic operation.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.