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