-
-
Notifications
You must be signed in to change notification settings - Fork 229
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
Dynamic linq not correctly apply on list of DynamicClass #593
Comments
It's a bug, as far as i can tell, but it is not related to encodings. The bug rather seems to be related to strings being interned (which string literals defined in the source code of the program typically are) vs. strings that are not interned. For reasons i don't know, the expression parser/language of dynamic linq does not like strings that are not interned (i.e., strings that are dynamically/programmatically created during program execution). And it so happens that Encoding.Default.GetString(utfBytes) creates a new string that is not interned. You should be able to reproduce the same bug effect without involving anything encoding-related at all by replacing Encoding.Default.GetBytes/Encoding.Default.GetString with something like: var cc = "firstValue".ToCharArray();
field.Value = new string(cc); (If you can reproduce the problem with the char array trick shown here, it would be nice if you could edit the title of your issue report and remove the mention of encodings from it so the project maintainers don't get send on the wrong track wrt to the underlying issue...) |
Some further insights about what's going on here... The System.Linq.Dynamic.Core library is building an System.Expressions.Expression tree from the string expression in The System.Linq.Dynamic.Core library tries to figure out the type of the left-hand expression (which is being just the identifier firstName. Since query is a List<DynamicClass>, it will reflect upon the type DynamicClass to find a field or property with the name firstName to figure out its type. But, of course, DynamicClass itself does not feature any firstName member, so System.Linq.Dynamic.Core is treating firstName as being of type object. And treating firstName as being of type object is the crux of the problem. The behavior emanates from the System.Linq.Expressions classes (provided by the .NET base class library). It can be reproduced with some simple System.Linq.Expressions.Expression setup without even involving System.Linq.Dynamic.Core: // lets first create two non-interned strings (i.e., not going to use string literals)
// Both strings have the same value, but since we don't use string literals, each string is a separate instance.
var s1 = new string(new char[] { 'f', 'i', 'r', 's', 't', 'V', 'a', 'l', 'u', 'e' });
var s2 = new string(new char[] { 'f', 'i', 'r', 's', 't', 'V', 'a', 'l', 'u', 'e' });
// Create left-hand and right-hand constant expressions, with the values of the strings s1 and s2.
// However, the left-hand expression will be of type object, whereas the right-hand expression will be of type string.
var left = System.Linq.Expressions.Expression.Constant(s1, typeof(object));
var right = System.Linq.Expressions.Expression.Constant(s2, typeof(string));
// now create the equality expression 'left equals right'
var eq = System.Linq.Expressions.Expression.Equal(left, right);
// and evaluate the equality expression
var lam = System.Linq.Expressions.Expression.Lambda(eq);
var fn = (Func<bool>) lam.Compile();
bool result = fn();
Console.WriteLine(result); Dotnetfiddle: https://dotnetfiddle.net/IArvJA This will result in the expression evaluating to (It really is the left-hand expression being typed as object. If it were typed as string, the result would be, as expected, true. https://dotnetfiddle.net/5VhuVF) |
Knowing this, a workaround is possible: bool isValid = query.Any("firstName.ToString() eq \"firstValue\""); Since basically any type derives from System.Object (except interfaces and some such, perhaps, i don't know really...), the ToString method (declared by System.Object) can be used to make the type of the left-hand expression here to be string (which is practical in this scenario here, since firstName contains a string value anyways), thus avoiding the issue. But i admit myself, it's not a pretty workaround... |
Thank you very much for your clear explanation. but this workaround not work for me because query expression ("firstName eq "firstValue"") capturing from external source and that can be more complex than the example. I have to use some tokenizer for identify properties and then I can use simple linq query without using dynamic linq. |
Thanks for the explanation. but @elgonzo when I overwrite |
Because you are using a string literal here. String literals are by default interned. If you are curious of how exactly this behavior materializes on a more technical level: Two aspects of System.Linq.Expressions conspire here. The first factor is that the eq operator is relying on System.Linq.Expressions.BinaryExpression. When comparing two exrepssions that are typed as reference types with System.Linq.Expressions.BinaryExpression, and one of the expressions (perhaps specifically the left-hand side expression only) is of type object, then the equality comparison is pretty much reduced to a simple reference comparison (similar to object.ReferenceEquals(o1,o2))). The second factor is that for the "firstValue" string in So basically, the library turns I only got to know this myself after asking about this behavior on dotnet's own github discussion section here: dotnet/runtime#70962 |
@anc-dev-lk |
@StefH |
Hello @elgonzo and @anc-dev-lk , I tried to reproduce your issue with this unit test, but I could not reproduce it. [Fact]
public void DynamicClassArray()
{
// Arrange
var field = new
{
Name = "firstName",
Value = "firstValue"
};
var dynamicClasses = new List<DynamicClass>();
var props = new DynamicProperty[]
{
new DynamicProperty(field.Name, typeof(string))
};
var type = DynamicClassFactory.CreateType(props);
var dynamicClass = (DynamicClass) Activator.CreateInstance(type);
dynamicClass.SetDynamicPropertyValue(field.Name, field.Value);
dynamicClasses.Add(dynamicClass);
var query = dynamicClasses.AsQueryable();
// Act
var isValid = query.Any("firstName eq \"firstValue\"");
// Assert
isValid.Should().BeTrue();
} Can you please help me building a unit test that fails? |
That is because the left-hand side of the query (the To reproduce the issue, you will need to avoid using string literals here, as string literals are interned by default. One way to get around this for the test is using one of the string constructors to dynamically create a string, or use any other string function that produces a new string instance dynamically (such as string.Concat, string.Substring, etc...). So, instead of
or
or any other code construct that avoids creating an interned string... dotnetfiddle showing the test failing when using a non-interned string for field.Value: https://dotnetfiddle.net/DHvT1f |
Hello @anc-dev-lk and @elgonzo , I can reproduce the issue using that example code and you analysis is correct. Another part of the issue is this code: var dynamicClass = (DynamicClass) Activator.CreateInstance(type); Because the result from CreateInstance (which is actually another type : <>f__AnonymousType0`1[System.String]), the information about the real properties, a string property, is lost. If your use case is this: The solution for your issue is just passing the data (field), create a list of field entities and query on the "Value", like this: var cc = "firstValue".ToCharArray();
var field = new
{
Name = "firstName",
Value = new string(cc)
};
var array = new[] { field }.AsQueryable();
var isValid = array.Select("it").Any("Value eq \"firstValue\"");
isValid.Should().BeTrue(); |
No, that's incorrect. The type information of the As i mentioned in my comment above the problem is related to using the type Rather, instead of using a But, of course, |
@elgonzo The problem is this example for 'Any' (using List) is that the Type, in the Any method, is retrieved using the ElementType which is DynamicClass. A solution for this is that the real type is provided as parameter to the Any method. Or getting the first element from the IQuerable and call GetType(). However I am not sure this can work will a real IQueryable which accesses a DB. However the question is if this a real problem in an application or a more technical -what if- issue. |
Hello @anc-dev-lk and @elgonzo, There are several solutions for this issue:
var customListType = typeof(List<>).MakeGenericType(dynamicInstance.GetType());
var isValid = query.Any("firstName.ToString() eq \"firstValue\"");
var isValid = query.Any("string(firstName) eq \"firstValue\""); |
I have following code. From external source I'm getting
field.Name = "firstName"
andfield.Value = "firstValue"
.But I'm getting
isValid
asfalse
.I did following tests to understand the issue:
added following code line at the beginning, to overwrite the value and then I'm getting
isValid
astrue
.but if I change that as follows then I'm getting
isValid
asfalse
.I'm guessing dynamic linq not working with string that has different encodings.
The text was updated successfully, but these errors were encountered: