Search code examples
c#linqasp.net-web-api

Proper way to compare request with entity


I have issue with implementation Get method into my manager class. How do i need to filter and where i need to write filter method.

In short - i have data class Gym, repository class and method Find in it. I wrote methods in data classes - IsAppreciateToRequest(RequestName) to do smth like this in manager class

public IEnumerable<GymDto> GetGyms(GetGymRequest request)
{
    return _gymRepository
           .Find(gym => gym.IsAppreciateToRequest(request))
           .AsEnumerable()
           .Select(GymDto.FromEntityToDto);
}

I think this is shitcode, but also idk how to get rid of this and how to write it proper way(before this i had Get method like 30-50 lines longer in every manager class)

IsAppreciateToRequest method:

 public bool IsAppreciateToRequest(GetGymRequest other)
        {
            return (string.IsNullOrEmpty(other.Name) || Name == other.Name)
                   &&  (string.IsNullOrEmpty(other.Location) || Location == other.Location) 
                   && (other.SectionRequest == null || Sections.All(section => section.IsAppreciateToRequest(other.SectionRequest)));
        }

Solution

  • You can use LINQKit for injecting Expression Tree into filters. It is needed to configure DbContextOptions:

    builder
        .UseSqlServer(connectionString) // or any other provider
        .WithExpressionExpanding();     // enabling LINQKit extension
    

    Your classes should be extended with static function which returns Expression<Func<>> and current methods should have ExpandableAttribute

    For example:

    public class Gym
    {
        [Expandable(nameof(IsAppreciateToRequestImpl))]
        public bool IsAppreciateToRequest(GetGymRequest other)
        {
            return (string.IsNullOrEmpty(other.Name) || Name == other.Name)
                    && (string.IsNullOrEmpty(other.Location) || Location == other.Location) 
                    && (other.SectionRequest == null || Sections.All(section => section.IsAppreciateToRequest(other.SectionRequest)));
        }
    
        private static Expression<Func<Gym, GetGymRequest, bool>> IsAppreciateToRequestImpl()
        {
            // first parameter is current object
            return (gym, other) => (string.IsNullOrEmpty(other.Name) || gym.Name == other.Name)
                    && (string.IsNullOrEmpty(other.Location) || gym.Location == other.Location) 
                    && (other.SectionRequest == null || gym.Sections.All(section => section.IsAppreciateToRequest(other.SectionRequest)));
        }
    }
    
    // the same technique as in Gym class
    public class Section
    {
        [Expandable(nameof(IsAppreciateToRequestImpl))]
        public bool IsAppreciateToRequest(GetSectionRequest other)
        {
            return // unknown;
        }
    
        private static Expression<Func<Section, GetSectionRequest, bool>> IsAppreciateToRequestImpl()
        {
            // first parameter is current object
            return (section, other) => // unknown;
        }
    }
    

    Then LINQKit will expand expressions returned by your static methods and will inject conditions into predicate.

    The same approach can be used for projecting entity to DTO. Similar my answer here, which also shows known alternatives for LINQKit.