I using Dynamic Linq to do some database queries and it's working really well up to now. I can pass a string to a Select to select fields like so:
var output = myDatabaseTable.Select("Foo, Bar");
For example. The power obviously being when you pass a string variable rather than hardcoded strings. The problem I'm running into now is that the library using IEnumerable instead of IEnumerable<T> because, obviously, it can't know T until runtime. I'm using this to select data and eventually return it to a client and it works fine for spitting out raw data, but now I want to be able to do some more processing before returning the data and that requires getting the query to run on the database first. I can do something like this:
var materializedResults = output.Cast<dynamic>().ToList();
And that will make the query run. But the problem is, once I've done that, it seems I can't use dynamic linq anymore. For example, if I did something like this:
var foos = materializedResults.Select("Foo");
I now get a System.Linq.Dynamic.ParseException with the message No property of field 'Foo' exists in type 'Object' (Note: I can see in the debugger that the materializedResults does actually have all the expected properties).
So after casting to a List so I can potentially iterate through it and modify some of the values, I can no longer query it.
So my question is, how can I take a dynamic query (with select, group by, order by etc provided as strings), materialize the results and then actually process those result dynamically?
I thought maybe if I could cast to the actual type rather than dynamic it might work, so I tried this:
var d = output.Cast<dynamic>().ToList();
MethodInfo method = typeof(Enumerable).GetMethod("Cast", new[] {typeof(IEnumerable)});
method = method.MakeGenericMethod(d.First().GetType());
output = method.Invoke(d, new[] {d}) as IEnumerable;
Which is ugly and requires me to cast twice. The first time to dynamic so I can get the type from the first item then again to that type.
If you do YourStuff.Cast<dynamic>.ToList(), you will receive IEnumerable<object>, and there is no property Foo on the type object.
The question you might be asking, how can you get IList<TheActualType>?! You can do it this way:
// for IEnumerable
public static IList ToAnonymousList(this IEnumerable enumerable)
{
var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
throw new Exception("?? No elements??");
var value = enumerator.Current;
var returnList = (IList) typeof (List<>)
.MakeGenericType(value.GetType())
.GetConstructor(Type.EmptyTypes)
.Invoke(null);
returnList.Add(value);
while (enumerator.MoveNext())
returnList.Add(enumerator.Current);
return returnList;
}
// for IQueryable
public static IList ToAnonymousList(this IQueryable source)
{
if (source == null) throw new ArgumentNullException("source");
var returnList = (IList) typeof (List<>)
.MakeGenericType(source.ElementType)
.GetConstructor(Type.EmptyTypes)
.Invoke(null);
foreach (var elem in source)
returnList.Add(elem);
return returnList;
}
It's a simple extension method that can later be used, as such:
var test = (new[]
{
new
{
Property1 = "10",
Property2 = "10",
Property3 = 1
}
}
.Select("New(Property1, Property2)"))
.ToAnonymousList();
Your Cast is Defaulting to which will cause an error
(output as System.Collections.Generics.IEnumerable)
This Cast Specified the Correct Interface Try again
(output as System.Collections.IEnumerable).Cast<dynamic>().ToList()
Related
This code snippet returns me an error,
public List<auto> autoSelect()
{
return autoSelect(DateTime.Today);
}
public List<auto> autoSelect(DateTime date)
{
var onderhoudAuto = (from onderhoud in db.onderhouds
where onderhoud.uitvoerdatum != DateTime.Today
select onderhoud)
.FirstOrDefault();
List<string> autos = (from auto in db.autos
where auto.autoId.Equals(onderhoudAuto)
select auto)
.FirstOrDefault();
return autos;
}
I tried convert the var to a list with .ToList(); although this doesn't work. Does anyone have any suggestions?
I tried convert the var to a list
No, you do not. var is not actually a data type - it is resolved by the compiler. A tooltip should show you the real type.
Your problem is different:
Looking at your code, we can see:
The method autoSelect signature states that the return type is List<auto>
public List<auto> autoSelect(DateTime date)
The variable autos type is List<string>
List<string> autos = [...etc...]
return autos;
You return autos, but it is not possible to return a List<string> when a List<auto> is expected.
So it has nothing to do with var - it is simply you selecting as single property and returning a list of strings, but that is not the type the method is supposed to return.
If you use FirstOrDefault() after your linq query, you are saying you want the first element (or the default -usually null- for the datatype if none matches) in the LINQ query, not a list of elements.
If you want a list of elements, use ToList() on the linq query, not try to convert a single entity to a list.
If you, for some reason, want a list of a single entity, then create a list (with new List<type>()) and then add your entity (of the same type as your list) to the list.
I have a simple need to filter all parents out of the returned collection where there is no match on a field, that is called by name from a string, doesn't match a value presented. What I am after is if parent object has child object, and that child objects property "foo"(called by string) doesn't or does equal a value bar, the parent object is filtered from the collection appropriately.
Here is my linq ef call
var field = "bar";
var values = new List<string>{"foo","fuYu"};
var dataPage = _aim_context.ae_s_bld_c.AsNoTracking();
var result = dataPage.Where(x =>
DbHelper.byPropertyContains(x.udfs, field, values)
);
// NOTE `udfs` is a ONE-to-ONE with `ae_s_bld_c`
What I am looking to see is something like the SQL of
SELECT [m].[id],[m.udfs].[bar],
FROM [dbo].[ae_s_bld_c] AS [m]
INNER JOIN [dbo].[ae_s_bld_c_udf] AS [m.udfs]
ON ([m].[multitenant_id] = [m.udfs].[multitenant_id])
WHERE ([m].[multitenant_id] = 1.0)
AND ([m.udfs].[bar] IN ('foo','fuYu')) --< Goal line
The way I have approached this was to get an expression set up to take the List<string> and make the SQL. I have read near 50 articles and SO posts, but have not figured out exactly why I am not getting this just yet as everyone seems to have different ideas, and most are not in line with dotnet core 2.1+ it seems.
Here is what I am sitting at currently after many many iterations. NOTE: it is a little different from what I am after as I am giving my current trail.
My current context linq try
//...
dataPage = dataPage.Where(DbHelper.byPropertyContains<ae_s_bld_c>("udfs", field, values));
//...
I think it would be better if it was like the first example I put up, but that was what I have landed on since I have had a time lining it up with x=>x.udfs, both as x=> funName(x.udfs) and x=> x.udfs.funName()
My static method to build the expression
public static class DbHelper
{
public static Expression<Func<T, bool>> byPropertyContains<T>(string node, string field, List<string> value) {
//trying to take parent item and get it's property by string name because
// doing the function in linq like x=>x.udfs was not working right
// but that is the prefered I think
var property_parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.PropertyOrField(property_parameter, node);
var selector_parameter = Expression.Parameter(property.Type, "y");
var selector = Expression.PropertyOrField(selector_parameter, field);
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] {
typeof(string)
});
var list = Expression.Constant(value, typeof(List<string>));
var body = Expression.Call(methodInfo, list, selector);
return Expression.Lambda<Func<T, bool>>(body, selector_parameter);
}
}
Update
Per the request of #NetMage I have tried to work backwards with LINQpad. I think I am close but it is hard to tell with teh output. I am putting it up here for reference. To be clear, the property name of the child will be a string of the name. The best outcome is I could have a name like udfs.foo where I can test on any level if the values contain by string name, but really ok with it starting here,
var result = dataPage.Where(x =>
DbHelper.byPropertyContains(x.udfs, field, values)
);
Let start from here. You need an equivalent of something like this
var result = dataPage.Where(x => values.Contains(x.udfs.{field}));
where field is a string returning property dynamically specified by name.
In EF Core you don't even need to deal with building expresions by hand, because EF Core provides a special SQL translatable function for accessing simple properties by name called EF.Property.
With that method the solution is simple as that:
var result = dataPage
.Where(x => values.Contains(EF.Property<string>(x.udfs, field)));
Full disclosure, I do not fully understand generics but am hoping the answer to this question will clear things up.
I have an application that allows a user to build a query of their own. The query will leverage the Entity Framework. I would like to return a list of the results whose type is dependent upon what column they queried. In other words i need a method that could have multiple return types. I derive the type using reflection on the column and then would like to pass to this method.
public static T getValues<T>(string ColName, Type type)
{
var result = db.AC_PROPERTY.Select(ColName);
if (type == typeof(string))
{
List<string> list = new List<string>();
//Query and add results to list.
return list;
}
if (type == typeof(double?) || type == typeof(double))
{
List<double> list = new List<double>();
//Query and add results to list.
return list;
}
if (type == typeof(int) || type == typeof(int?))
{
List<int> list = new List<int>();
//Query and add results to list.
return list;
}
if (type == typeof(DateTime))
{
List<DateTime> list = new List<DateTime>();
//Query and add results to list.
return list;
}
}
I am getting an error stating that I cannot implicitly convert List (or the respective type) to T.
Can someone explain this and perhaps walk me through how to accomplish this.
Ask yourself this: How are T and Type type meaningfully different? Why do you need both?
You don't.
Furthermore, while it's technically possible to check for a generic parameter's type, you should not be doing this. It's a code smell. If your method is generic, the method body should be generic as well. This includes not needing to know the exact value of T.
1. Get rid of Type type
public static List<T> getValues<T>(string ColName)
{
//...
}
Notice how this method would be used:
List<DateTime> myDateList = getValues<DateTime>("MyDateTimeColumn");
2. Make the method body generic
public static List<T> getValues<T>(string ColName)
{
List<T> myReturnList = new List<T>();
var selectedColumnValues = db.AC_PROPERTY.Select(ColName);
//Query and add results to list.
return myReturnList;
}
Looks a lot neater, doesn't it? I noticed you omitted the actual creating of the list; so I omitted it too for now.
I could get into the rest of the method body based on some assumptions, but there's a much more relevant consideration here.
Your method seems to be no different from LINQ's Select() method!
So instead of doing this with your custom built getValues<T> method:
List<DateTime> myDateList = getValues<DateTime>("MyDateTimeColumn");
You can use the existing LINQ method:
List<DateTime> myDateList = myDataList.Select(item => item.MyDateTimeProperty).ToList();
(Note: ToList() can be optional but is advisable due to lazy evaluation).
I have the following code:
public IList<MyObject> GetSomeData(string inputParam)
{
var temp = repository.GetData(inputParam);
var list = temp as List<MyObject>;
return list;
}
The return value of repository.GetData is IEnumerable<IMyObject>
When I look at the value of temp, it has 400+ records. The moment I cast it to list, it becomes null. Why is this cast not possible?
It returns null because that IEnumerable isn't actually a list. The cast will only succeed if that particularly IEnumerable happens to be a List, instead of some other type of sequence. If you want to have a list, you will need to create a new list and add the items from the sequence into that list.
Unless the underlying object of the value returned from repository.GetData matches what you are trying to cast it to then the result will always be null. Because the generic element types of the method and what is actually return from repository.GetData are different you will need to do some conversions to get the desired result
Assuming that MyObject implements IMyObject I can think of at least to ways using System.Linq to get the result you seek.
Option 1: Cast<T>()
Casts the elements of an System.Collections.IEnumerable to the specified type.
First convert the content of temp using the Cast<MyObject>() linq extension and then use the ToList<T>() extension method to get you resulting IList<MyObject>
public IList<MyObject> GetSomeData(string inputParam)
{
//repository.GetData returns IEnumerable<IMyObject>
var temp = repository.GetData(inputParam);
var list = temp.Cast<MyObject>().ToList();
return list;
}
Option 2: OfType<T>()
Filters the elements of an System.Collections.IEnumerable based on a specified type.
Filter the content of temp using the OfType<MyObject>() linq extension and then use the ToList<MyObject>() extension method to get you resulting IList<MyObject>
public IList<MyObject> GetSomeData(string inputParam)
{
//repository.GetData returns IEnumerable<IMyObject>
var temp = repository.GetData(inputParam);
var list = temp.OfType<MyObject>().ToList();
return list;
}
I have to write a query in a web application using LINQ but I need to change that query into an array list. How can I change the query below to do this?
var resultsQuery =
from result in o["SearchResponse"]["Web"]["Results"].Children()
select new
{
Url = result.Value<string>("Url").ToString(),
Title = result.Value<string>("Title").ToString(),
Content = result.Value<string>("Description").ToString()
};
If you really need to create an ArrayList, you can write new ArrayList(resultsQuery.ToArray()).
However, you should use a List<T> instead, by writing resultsQuery.ToList().
Note that, in both cases, the list will contain objects of anonymous type.
There is a .ToArray() method that'll convert IEnumerable to an Array.
ArrayList doesn't have a constructor or Add(Range) method that takes an IEnumerable. So that leaves two choices:
Use an intermediate collection that does implement ICollection: as both Array and List<T> implement ICollection can be used via the ToArray() or ToList() extension methods from LINQ.
Create an instance of ArrayList and then add each element of the result:
var query = /* LINQ Expression */
var res = new ArrayList();
foreach (var item in query) {
res.Add(item);
}
The former method is simple to do but does mean creating the intermediate data structure (which of the two options has a higher overhead is an interesting question and partly depends on the query so there is no general answer). The latter is more code and does involve growing the ArrayList incrementally (so more memory for the GC, as would be the case for an intermediate Array or List<T>).
If you just need this in one place you can just do the code inline, if you need to do it in multiple places create your own extension method over IEnumerable<T>:
public static class MyExtensions {
public static ArrayList ToArrayList<T>(this IEnumerable<T> input) {
var col = input as ICollection;
if (col != null) {
return new ArrayList(col);
}
var res = new ArrayList();
foreach (var item in input) {
res.Add(item);
}
return res;
}
}