LINQ. Złożony warunek Where

0

Witajcie. Mam problem logiczny tak na prawdę, otóż:

Piszę akcję wyszukania do api. Próbuję wyciągnąć z bazy danych informacje.
dowolny znak -- może być pusty
data urodzenia od -- może być nullem
data urodzenia do -może być nullem
znak zodiaku - może być nullem

robię to w ten sposób:

_context.Users.Include(q => q.roles)
.Where(
f => f.FirstName.Contains(search.Keyword) ||
f.LastName.Contains(search.Keyword) &&
f.DateOfBirth >= search.DateOfBirthFrom &&
f.DateOfBirth <= search.DateOfBirthTo &&
f.znakzodiaku== search.znakzodiaku
).ToList();

To działa w przypadku gdy klient poda wszystkie dane ale co w przypadku gdy nie poda? Gdy poda tylko jedną?
Próbowałem zrobić to dodając do każdego wyrażenia and(&&) z !string.IsNullOrWhiteSpace(search.Keyword) ale niestety wychodziły róże kwiatki. Może ktoś mi poradzi jak się do tego zabrać?

Pozdrawiam!

0

Jak do tego co próbujesz zrobić potrzebne, są Ci te dane to wymuś, aby były jak nie rzuć wyjątkiem.
przykład

public class User
{
public string Name {get; private set;}
public User(string name)
{
Name = name ?? throw new ArgumentException();
}
}
3

Albo budujesz wyrażenie dynamicznie, czyli:

if (search.DateOfBirthFrom != null){
  query = query.Where(f=> f.DateOfBirth >= search.DateOfBirthFrom);
}

itd. z wszystkimi warunkami.

Opcja b, to odpowiednie pogrupowanie warunków:

&& (f.DateOfBirth >= search.DateOfBirthFrom || search.DateOfBirthFrom == null)
&& ...
1

Rozwiązanie nr. 1 @czesiek jest praktyczne, czytelne i łatwe do zmiany, ale jest tak średnio dynamiczne

da się to uczynić bardziej dynamicznym, ale trzeba się trochę nakodzić oraz przemyśleć użycie.

W tym przypadku zrobiłem tak, że NazwaProperty + słówko "Operator" definiuje jaki ma być operator ma być użyty w lambdzie (>, <, ==, >= itd.)

public class User
{
    public User(string firstName, bool isProfientAtJava, int PizzasPerMonth)
    {
        FirstName = firstName;
        IsProfientAtJava = isProfientAtJava;
        SalaryAsPizzasPerMonth = PizzasPerMonth;
    }

    public string FirstName { get; set; }

    public bool IsProfientAtJava { get; set; }

    public int SalaryAsPizzasPerMonth { get; set; }
}

public class SearchInput
{
    public string FirstName { get; set; }

    public string FirstNameOperator { get; set; }

    public bool? IsProfientAtJava { get; set; }

    public string IsProfientAtJavaOperator { get; set; }

    public int? SalaryAsPizzasPerMonth { get; set; }

    public string SalaryAsPizzasPerMonthOperator { get; set; }
}

private static Expression GetPredicateExpression(Expression param, string paramName, string operatorr, object value)
{
    switch (operatorr)
    {
        case "Equal":
            return Expression.Equal
            (
                Expression.Property(param, paramName),
                Expression.Constant(value)
            );

        case "GreaterThan":
            return Expression.GreaterThan
            (
                Expression.Property(param, paramName),
                Expression.Constant(value)
            );
        default:
            throw new Exception("invalid operator");
    }
}

public static Func<User, bool> ObtainLambda(string paramName, object value, string operatorr)
{
    var param = Expression.Parameter(typeof(User), "p");

    var expression = GetPredicateExpression(param, paramName, operatorr, value);

    var exp = Expression.Lambda<Func<User, bool>>
    (
        expression,
        param
    );

    return exp.Compile();
}

I tutaj użycie: wyciągnij Tomów, którzy zarabiają > 30

var searchInput = new SearchInput
{
    FirstName = "Tom",
    FirstNameOperator = "Equal",
    SalaryAsPizzasPerMonth = 30,
    SalaryAsPizzasPerMonthOperator = "GreaterThan"
};

var users = new List<User>
{
    new User("Tom", true, 25),
    new User("NotTom", true, 40),
    new User("Tom", true, 70),
    new User("Test", false, 31),
    new User("Tom", false, 31),
    new User("Tom", true, 30),
};

var query = users;

var properties = searchInput.GetType().GetProperties();

foreach (PropertyInfo propertyInfo in properties.Where(x => x.GetValue(searchInput) != null && !x.Name.Contains("Operator")))
{
    var name = propertyInfo.Name;
    var value = propertyInfo.GetValue(searchInput);

    var operatorr = properties.FirstOrDefault(x => x.Name == $"{name}Operator")?.GetValue(searchInput)?.ToString() ?? "operator not found";

    var avaliableOperators = new[] { "Equal", "GreaterThan" };

    if (!avaliableOperators.Contains(operatorr))
        throw new Exception($"operator '{operatorr}' is not supported.");

    query = query.Where(ObtainLambda(name, value, operatorr)).ToList();
}

foreach (var user in query)
{
    Console.WriteLine(user.FirstName);
    Console.WriteLine(user.IsProfientAtJava);
    Console.WriteLine(user.SalaryAsPizzasPerMonth);
    Console.WriteLine();
}

Wynik:

Tom
True
70

Tom
False
31

Lub

var searchInput = new SearchInput
{
    IsProfientAtJava = false,
    IsProfientAtJavaOperator = "Equal"
};

Wynik:

Test
False
31

Tom
False
31
0

Osobiście wolałbym podjeście pierwsze bo zajmuje mniej kodu i jest czytelniejsze. Jednak w moim przypadku ono również nie działa... :(

edit
A jednak nie. Tak jak widziałeś w pierwszym poście próbowałem pierwszego podejścia wczoraj przez 3 godziny. Dopiero dziś tak na prawdę wziąłem to na logikę(logikę na logikę :D) i zauważyłem, że mój błąd był tak na prawdę w niezrozumieniu tego.
Dla innych porada:
Przeczytać to kilka razy i sobie kilka razy dobrze w głowie ułożyć.

Pozdrawiam i dziękuję!

0

A problem czasami nie tkwił po prostu w tym że używałes !string.IsNullOrWhiteSpace(search.Keyword) (z negacją na początku) zamiast właśnie string.IsNullOrWhiteSpace(search.Keyword) || <inny warunek>? A więc pole musi być puste albo warunek musi zostać spełniony.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0