-
-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Add the "?." operator (null-conditional operator) to support navigation properties with null values #98
Comments
You could use the |
It seems not to work with navigation properties. Ex:
Throws the exception. Finally, I could solve it with: It would be great for future versions if you consider supporting the ? operator to improve this kind of queries. Thanks again! |
Actually you want the |
Yes. I'm having problems because of the implicit conversion of types. In the example before, if Value of RelatedItem is a non-nullable type (double, int...) I cannot use a expression like "Related == null ? null : Related.Value", it throws an exception. |
@jotab123 See also here: So I can try to add this functionality, but probably it wont work... |
Hi,
Thank you |
Hi @peter-base ,
Hope it helps! |
Hi Stef, I've been researching about null-conditional operator and I found some interesting things.
And now, I can use this kind of query:
Could you consider and test this code to include it in your library? Regards! |
I have to take a look at your code and update some unit tests. |
Another option would be to pre-process the string, so that "it.Relation1?.Relation2?.Id != null" becomes "(it.Relation1 != null ? (it.Relation1.Relation2 != null ? it.Relation1.Relation2.Id : null) : null) != null" |
@StefH Hi, It's what I did on my side. This is how I do it: private static string IgnoreIfNull(string fieldNames)
{
string[] splits = fieldNames.Split('?.');
for (int i = 0; i < splits.Count() - 1; i ++)
{
string toBeReplaced = string.Join('.', splits.Take(i + 1));
fieldNames = fieldNames.Replace(toBeReplaced, $"{toBeReplaced} == null ? null : ({toBeReplaced}") + ")";
}
return fieldNames;
} It will transform: "it?.Relation1?.Relation2?.Id" into "(it == null ? null : (it.Relation1 == null ? null : (it.Relation1.Relation2 == null ? null : it.Relation1.Relation2.Id)))" |
@AmbroiseCouissin This looks like a possible solution, however where to execute this code? |
@StefH In my case, I use this way:
|
Any progress on this? By the way @AmbroiseCouissin I wrote something like your code. This is more failproof and also null-checks even if no null coalescing operator is defined: public static class DynamicQueryExtensions
{
public static string ConvertToNullableNested(string expression, string result = "", int index = 0)
{
//Transforms => "a.b.c" to "(a != null ? (a.b != null ? a.b.c : null) : null)"
if (string.IsNullOrEmpty(expression))
return null;
if (string.IsNullOrEmpty(result))
result = expression;
var properties = expression.Split(".");
if (properties.Length == 0 || properties.Length - 1 == index)
return result;
var property = string.Join(".", properties.Take(index + 1));
if (string.IsNullOrEmpty(property))
return result;
result = result.Replace(expression, $"({property} != null ? {expression} : 0)");
return ConvertToNullableNested(expression, result, index + 1);
}
} I use it the same way you specified: query.OrderBy($"{DynamicQueryExtensions.ConvertToNullableNested("Entity.Relation1.Relation2.Id")} desc"); |
@1dot44mb Thanks. I did fix a small issue and added generic support. public static string ConvertToNullableNested<T>(string expression, object defaultValue = null)
{
return ConvertToNullableNested<T>(expression, string.Empty, 0, defaultValue);
}
public static string ConvertToNullableNested<T>(string expression, string result, int index, object defaultValue)
{
if (string.IsNullOrEmpty(expression))
{
return null;
}
if (string.IsNullOrEmpty(result))
{
result = expression;
}
string[] properties = expression.Split('.');
if (properties.Length == 0 || properties.Length - 1 == index)
{
if (typeof(T).IsValueType)
{
return result.Replace(expression, $"{typeof(T).Name}?({expression})");
}
return result;
}
string property = string.Join(".", properties.Take(index + 1));
if (string.IsNullOrEmpty(property))
{
return result;
}
string defaultReplacement = defaultValue != null ? defaultValue.ToString() : "null";
result = result.Replace(expression, $"({property} != null ? {expression} : {defaultReplacement})");
return ConvertToNullableNested<T>(expression, result, index + 1, defaultValue);
} Can be used like: string finalT = ConvertToNullableNested<int>("it.Relation1.Relation2.Id");
// (it != null ? (it.Relation1 != null ? (it.Relation1.Relation2 != null ? Int32?(it.Relation1.Relation2.Id) : null) : null) : null)
string finalTDef = ConvertToNullableNested<int>("it.Relation1.Relation2.Id", default(int));
// (it != null ? (it.Relation1 != null ? (it.Relation1.Relation2 != null ? Int32?(it.Relation1.Relation2.Id) : 0) : 0) : 0) |
@1dot44mb So this code will work now: var r1 = q.Select("it != null && it.NestedDto2 != null ? it.NestedDto2.Id : null"); Previously you need to use this code: var r1 = q.Select("it != null && it.NestedDto2 != null ? int?(it.NestedDto2.Id) : null"); This new logic can be used in combination with A simpler approach for that helper method can also be: string expression = "it.Relation1.Relation2.Id";
string[] properties = expression.Split('.');
var list = new List<string>();
for (int idx = 0; idx < properties.Length - 1; idx++)
{
string property = string.Join(".", properties.Take(idx + 1));
list.Add($"{property} != null");
}
string str = $"({string.Join(" && ", list)} ? {expression} : null)"; This code generates a string like:
|
Thanks. |
If you want to this, you can already use this NuGet: |
@1dot44mb I've added more logic in PR #223 [not yet merged...] var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id, 0)"); // returns int's
// or
var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id)"); // returns nullable int's NuGet can be found at: |
@jotab123 If you have time, can you do some testing with this NuGet: Null Propagating has been implemented like: var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id, 0)"); // returns int's
// or
var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id)"); // returns nullable int's |
Dear @StefH I really appreciate the hard work. Until new year, i am a bit busy with some deadlines and projects. After that, i will happily test the nuget. Sorry for not being able to help sooner. |
@1dot44mb Thanks for your help. |
@1dot44mb Did you have time to check this new code? |
Hello @jotab123, @peter-base, @1dot44mb, @AmbroiseCouissin : can you please test this functionality? |
closed via PR |
@StefH
? Because I get exception (version
for
Without
|
@MaklaCof var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id, 0)"); // returns int's
// or
var q1 = q.Select("np(it.NestedDto2.NestedDto3.Id)"); // returns nullable int's |
I probably should have added this to the |
Hi, I'm trying to use the np function on a Where: e.g User.Contact.FirstName.ToLower().Contains("or") where the User can be null, so I was trying to get the User?.Contact?.FirstName. Maybe I don't really know how to use your library, but can you help me? Entity.Where ("np(User.Contact.FirstName.ToLower().Contains("tor")") and I get The 'np' (null-propagation) function requires the first argument to be a MemberExpression Maybe you see something I don't. Thanks. |
If the User can be null, just use this code Entity.Where ("User != null && User.Contact.FirstName.ToLower().Contains(\"tor\")"); |
Thanks. is there no way to use the np with the Where? Thanks again |
Sure you can use the
results in: -- Region Parameters
DECLARE @p0 NVarChar(1000) = ''
DECLARE @p1 NVarChar(1000) = '%e%'
-- EndRegion
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [Orders] AS [t0]
LEFT OUTER JOIN [Customers] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE (
(CASE
WHEN [t0].[CustomerID] IS NOT NULL THEN [t1].[City]
ELSE CONVERT(NVarChar(15),@p0)
END)) LIKE @p1 |
Thanks, Everything seems to be working, except for dates. If I do .OrderBy("np(Customer.EndDate)"), we are getting a System.DateTime cannot be null value. I will try a couple more things. |
You mean this error?
|
Hi, yes. It is fine if we use any other null columns |
I've created a new Issue |
Hi, I have an expression like this where e.g. myList is a srting lis and a member of dataSource;
does not work as expression, the LambdaExpression looks as following:
The C# way would look like this:
what would work.. |
What happens if you use var expressionText = "np(myList.FirstOrDefault)" |
np(myList.FirstOrDefault) throws an exception with:
|
and |
Exception = {"The 'np' (null-propagation) function requires the first argument to be a MemberExpression"} But dataSource has a member that is called myList |
This should work. Do you have the latest version? |
I have updated to the latest nuget version.. should I use a myget version? |
Maybe it is because I am using the DynamicParser? |
DynamicParsers uses same code. In my test, I use Maybe this only works like this. Else, can you open a new issue with an complete C# example? |
What is striking, is that I am not using code inside a select or a where clause. I am only using an expression based on an object :
|
Opened a new issue based on the information, I think it is because of the primitive list type. #366 |
Hi Stef,
I'm trying to project a sequence of items with a related property (object) which in some cases is null.
Having this classes:
Consider this scenario:
Result1 works fine and has the expected result (a list of items with Related.Value value or null, in case of Item has Related object assigned or not) but Result2 generates an object reference exception. Is there any way to achieve this?
Thanks!
System.Linq.Dynamic.Core version 1.0.7.6
The text was updated successfully, but these errors were encountered: