1

my question is simple but I got stuck with something. Can you tell me how can I reduce 2 select into 1 select LINQ in c#? I am using CloudNative.CloudEvents NuGet package for cloud-native events.

var orderEvents = input
    .Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
    .Select(_ =>
        new CloudEvent()
        {
            Type = _.EventType,
            Subject = _.Subject,
            Source = _.Source,
            Data = _
        });

input is a parameter from cosmosDbTrigger it`s type : IReadOnlyList

OrderDocument.cs

public class OrderDocument
{
    public string Id { get; private set; }

    public string Json { get; private set; }

    public OrderDocument(string id, string json)
    {
        Id = id;
        Json = json;
    }

    public OrderEvent ToOrderEvent() => OrderEventHelper.ToOrderEvent(Json);
}

OrderEventHelper.cs

public static OrderEvent ToOrderEvent(string json)
{
    ArgumentHelper.ThrowIfNullOrEmpty(json);

    var orderEvent = JsonConvert.DeserializeObject<OrderEvent>(json);
    var eventDefinition = OrderEvents.EventDefinitions.SingleOrDefault(_ => _.EventType == orderEvent.EventType);

    
    return eventDefinition == null
        ? orderEvent
        : new OrderEvent(
            orderEvent.Id,
            orderEvent.Source,
            orderEvent.EventType,
            orderEvent.Subject,
            orderEvent.DataContentType,
            orderEvent.DataSchema,
            orderEvent.Timestamp,
            JsonConvert.DeserializeObject(orderEvent.Payload.ToString(), eventDefinition.PayloadType),
            orderEvent.TraceId);

}
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
Penguen
  • 16,836
  • 42
  • 130
  • 205
  • 2
    TBH I highly doubt that your performance issues (if you have any) will be fixed by reducing number of selects. Have you profiled your application? Do you know what actually the hotspot is? – Guru Stron Jun 27 '22 at 15:58
  • 1
    You should avoid using the discard operator `_` as an actual variable you consume. `_.Id` is asking for future readers of your code to be confused. – Kirk Woll Jun 27 '22 at 16:14
  • @GuruStron I didn`t measure performance by using profiler or anything for my issue but Undoubtedly, select().select() is not the best practice and it will produce a couple of performance issues. – Penguen Jun 27 '22 at 16:23
  • 1
    You can do both actions in one select by passing a function through the `Select` rather than just returning the `OrderEvent`, see my updated answer for an example. – Ibrennan208 Jun 27 '22 at 16:25
  • 1
    @Penguen Look into Deferred Execution, the chained `Select` should still only iterate your collection once: https://stackoverflow.com/questions/7324033/what-are-the-benefits-of-a-deferred-execution-in-linq – Ibrennan208 Jun 27 '22 at 16:31
  • 1
    @Ibrennan208 why do you address this to me? =) – Guru Stron Jun 27 '22 at 16:34
  • 1
    @GuruStron Haha, my mistake, I fixed it. I really just wanted to type your name :p – Ibrennan208 Jun 27 '22 at 16:35
  • @Penguen while I agree that chained selects are not that great, but from the performance standpoint of view for LINQ-to-Objects the difference should be minimal (for ORMs - it can depend on actual ORM and database). – Guru Stron Jun 27 '22 at 16:39

2 Answers2

2

linq extensions are basically for loops in the background. If you want to perform multiple actions against a list, perhaps making your own simple for loop where you can manage that yourself would work.

Your code:

var orderEvents = input
    .Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
    .Select(_ =>
        new CloudEvent()
        {
            Type = _.EventType,
            Subject = _.Subject,
            Source = _.Source,
            Data = _
        });

could be changed to:

// our result set, rather than the one returned from linq Select
var results = new List<CloudEvent>();

foreach(var x in input){
   // create the order event here
   var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();
   
   // add the Cloud event to our result set
   results.Add(new CloudEvent() 
   {
       Type = temporaryOrderEvent .EventType,
       Subject = temporaryOrderEvent .Subject,
       Source = temporaryOrderEvent .Source,
       Data = temporaryOrderEvent 
   });
}

where you then have a result list to work with.

If you wanted to keep it all in linq, you could instead perform all of your logic in the first Select, and ensure that it returns a CloudEvent. Notice here that you can employ the use of curly brackets in the linq statement to evaluate a function rather than a single variable value:

var orderEvents = input
    .Select(x => 
    {
       // create the order event here
       var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();

       // return the Cloud event here
       return new CloudEvent() 
                  {
                       Type = temporaryOrderEvent .EventType,
                       Subject = temporaryOrderEvent .Subject,
                       Source = temporaryOrderEvent .Source,
                       Data = temporaryOrderEvent 
                  };
    });

Ibrennan208
  • 1,345
  • 3
  • 14
  • 31
1

How about putting conversion to OrderEvent and using ToCloudEvent in the same Select?

var orderEvents = input
    .Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent().ToCloudEvent())


public class OrderEvent
{
    public CloudEvent ToCloudEvent()
    {
        new CloudEvent()
        {
            Type = this.EventType,
            Subject = this.Subject,
            Source = this.Source,
            Data = this
        };
    }
}
ProgBlogger
  • 175
  • 1
  • 6
  • 23