-2

I have the following code everywhere in the project and I was wondering if i can create a extension method on IQueryable<T> that would cleanup the code in some places.

I googled without any luck.

this is part of the code for which i would like to convert into extension method.

 .OrderByDescending(y => y.LastUpdated != null ? y.LastUpdated : y.Created)

full query

  patient.PatientAddresses = patient.PatientAddresses
                   .Where(x => x.Deleted == false && x.Active == true)
                   .OrderByDescending(y => y.LastUpdated != null ? y.LastUpdated : y.Created)
                   .ToList();

help appreciated.

patel.milanb
  • 5,822
  • 15
  • 56
  • 92

4 Answers4

4

Extending the method on a generic type wouldn't give you access to the properties to sort by. Therefore you would need to create an extension method for every type (or constrain the extension), if you want to go with the extension method path.

The extension method would be made on IQueryable<PatientAddresses> such as this:

public static class Extensions
{
    public static IQueryable<PatientAddresses> MyLastUpdatedCustomOrder(this IQueryable<PatientAddresses> input)
    {
        return input.OrderByDescending(y => y.LastUpdated != null ? y.LastUpdated : y.Created);
    }
}

This will only work for IQueryable though so if you are trying to order an IEnumerable then you might need to cast it the query to AsQueryable() first such as:

patient.PatientAddresses = patient.PatientAddresses
               .Where(x => x.Deleted == false && x.Active == true)
               .AsQueryable()
               .MyLastUpdatedCustomOrder()
               .ToList();

Or create the same extension method for all the different types but this could get complicated fast. e.g. List, IList, ICollection, IEnumerable, etc.

An alternative without needing to create and manage lots of extension methods and worry about casting. Is to have a shared expressions which could be passed into the query.

e.g.

patient.PatientAddresses = patient.PatientAddresses
                   .Where(x => x.Deleted == false && x.Active == true)
                   .OrderByDescending(Ordering.LastUpdated)
                   .ToList();

with the order expression defined as:

public static class Ordering
{
    public static Expression<Func<PatientAddresses, DateTime>> LastUpdated = y => y.LastUpdated != null ? y.LastUpdated : y.Created;
}
Joe_DM
  • 985
  • 1
  • 5
  • 12
  • this is nice... let me try this one – patel.milanb Sep 05 '20 at 06:12
  • _"Extension methods cannot be created on generic types yet"_ - what do you mean by that? – CodeCaster Sep 05 '20 at 08:22
  • @CodeCaster Unless I'm missing an update generic types simply don't support extension methods. e.g. A `List` is a generic type. I can't extend `List` to have a `.Shuffle()` extension method for both `List` and `List`. I would have to create two extension separately for every type of list. I know theres talk about supporting generic extension methods and even static extension methods at a future date, however to my knowledge they don't yet exist. – Joe_DM Sep 05 '20 at 08:35
  • [You can just do that if you want](https://www.ideone.com/jXBh2W). If you want to do something meaningful to T inside the extension method, you must constrain it to a common denominator. See also my answer. – CodeCaster Sep 05 '20 at 08:42
  • 1
    I'm playing around in linqpad and yes, I'm wrong. I'll edit the answer. – Joe_DM Sep 05 '20 at 08:48
2

You can create an extension method, but for your question you can only make extension for specific type. You can do lot more with extension but here I have added code only for your request.

public static class MyExtension
    {
        public static IOrderedEnumerable<PatientAddresses> MyCustomOrderBy(this IEnumerable<PatientAddresses>  patientAddresses)
        {
            return patientAddresses.OrderByDescending(y => y.LastUpdated != null ? y.LastUpdated : y.Created);
        }
    }
Purushothaman
  • 519
  • 4
  • 16
1

If you want to apply this method to multiple entities, you have to introduce an interface that contains the relevant properties, an extension method that works on that interface and apply the interface to the relevant entities.

So:

public interface IHasTimestamps
{
    DateTime Created { get; set; }
    DateTime? LastUpdated { get; set; } 
}

public class Foo : IHasTimestamps
{
    public DateTime Created { get; set; }
    public DateTime? LastUpdated { get; set; }  
}

public class Bar : IHasTimestamps
{
    public DateTime Created { get; set; }
    public DateTime? LastUpdated { get; set; }  
}

Now you can create a generic extension method that accepts an IQueryable<T>, with T constrained to that interface, allowing you to access those properties:

public static class EntityExtensions
{
    public static IOrderedQueryable<TEntity> OrderByTimestampsDescending<TEntity>(this IQueryable<TEntity> query)
        where TEntity : IHasTimestamps
    {
        return query.OrderByDescending(q => q.LastUpdated ?? q.Created);
    }
}

And call it like that:

var list = new List<Foo>
{
    new Foo { Created = new DateTime(2020, 09, 03) },   
    new Foo { LastUpdated = new DateTime(2020, 09, 04) },   
    new Foo { Created = new DateTime(2020, 09, 05), LastUpdated = new DateTime(2020, 09, 06) }, 
}.AsQueryable();

var sorted = list.OrderByTimestampsDescending().ToList();

foreach (var s in sorted)
{
    Console.WriteLine(s.Created + " " + s.LastUpdated); 
}

So now you can call OrderByTimestampsDescending() on any IQueryable<T> where that T implements said interface, and have your results sorted.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
0

If you are not familiar with extension method, read Extension Methods Demystified

So in your query, every y is an object of class PatientAddress. You want to sort the patientAddresses, You plan to use this ordering on several locations and you want to hide how they are sorted.

The advantage is that you can be certain that everyWhere you order the PatientAddresses, they will be ordered in the same way. If later you want to sort on different properties, or reverse the sort order, you have to change this only on one location. Besides, you only have to unit test on one method.

The extension method will be:

public static IQueryable<PatientAddress> Order(this IQueryable<PatientAddress> patientAddresses)
{
    // TODO: decide what to do if patienAddresses== null.
    return patientAddresses.OrderByDescending(
        y => y.LastUpdated != null ? y.LastUpdated : y.Created)
}

Usage:

var result = fetchPatients().Where(patient => patient.Gender == Gender.Male)
                            .Select(patient => patient.Address)
                            .Order();

I choose to hide in the method name whether the Addresses are ordered ascending or descending. Of course if you need both methods, or need several ordering methods, they should be in the name:

var x = patienAddresses.OrderByPostCode();
var y = patientAddresses.OrderByDescendingCity();
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116