Convert Linq query to List without using generic parameter - linq

If I have an unevaluated query:
var q = (my linq query);
That happens to return a type of DbSet<T> and I need it to resolve to a List<T> because I'm about to lose the context within which it was created.
Normally I would just say q.Cast<T>.ToList() but at this place in the code I don't have T so how do I do this?
I do have the System.Type of T however. I came up with a work around but I am having to load a new List<T> (created with reflection) with the results of q. I don't like it because it's awkward and I'm loading another list but maybe loading a list is what ToList() does anyway.
Is there a better way?
public static System.Collections.IEnumerable RootCollection(string collectionPropertyName)
{
using (var db = new Model1())
{
var col = GetRootCollection(db, collectionPropertyName);
System.Type generic = typeof(List<>);
System.Type constructed = generic.MakeGenericType(col.GetType().GetGenericArguments().First());
var list = constructed.GetConstructor(new Type[] {col.GetType() }).Invoke(new object[] { col });
return (System.Collections.IEnumerable)list;
}
}

Related

Generic expression for where clause - "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."

I am trying to write a really generic way to load EF entities in batches, using the Contains method to generate a SQL IN statement. I've got it working if I pass the entire expression in, but when I try to build the expression dynamically, I am getting a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." So I know this means that EF thinks I'm calling an arbitrary method and it can't translate it into SQL, but I can't figure out how to get it to understand the underlying expression.
So If I do something like this (just showing the relevant snippets):
Function declaration:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<T, int> entityKey, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Append a where clause to the query passed in, that will use a Contains expression, which generates a SQL IN statement. So our SQL looks something like
// WHERE [ItemTypeId] IN (1921,1920,1922)
// See http://rogeralsing.com/2009/05/21/entity-framework-4-where-entity-id-in-array/ for details
Func<int[], Expression<Func<T, bool>>> containsExpression = (entityArray => (expr => entityArray.Contains(entityKey(expr))));
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
Call function:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (ek => ek.ItemTypeId)
);
I get "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."
But if I change it to be this:
Function declaration:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<int[], Expression<Func<T, bool>>> containsExpression, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
Call function:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (entityArray => (ek => entityArray.Contains(ek.ItemTypeId)))
);
It works fine. Is there any way I can make EF understand the more generic version?
The problem, as you describe, is that the entityKey function in the first example is opaque since it is of type Func rather than Expression. However, you can get the behavior you want by implementing a Compose() method to combine two expressions. I posted the code to implement compose in this question: use Expression<Func<T,X>> in Linq contains extension.
With Compose() implemented, your function can be implemented as below:
public static List<T> Load<T>(this IQueryable<T> entityQuery,
int[] entityIds,
// note that this is an expression now
Expression<Func<T, int>> entityKey,
int batchSize = 500,
Expression<Func<T, bool>> postFilter = null)
where T : EntityObject
{
Expression<Func<int, bool>> containsExpression = id => entityIds.Contains(id);
Expression<Func<T, bool>> whereInEntityIdsExpression = containsExpression.Compose(entityKey);
IQueryable<T> filteredById = entityQuery.Where(whereInEntityIdsExpression);
// if your post filter is compilable to SQL, you might as well do the filtering
// in the database
if (postFilter != null) { filteredById = filteredById.Where(postFilter); }
// finally, pull into memory
return filteredById.ToList();
}

linq: Using methods in select clause

I'm breaking my head with this and decided to share my problem with you
I want to create an anonymous select from several tables, some of them may contain more than one result. i want to concatenate these results into one string
i did something like this:
var resultTable = from item in dc.table
select new
{
id= item.id,
name= CreateString((from name in item.Ref_Items_Names
select name.Name).ToList()),
};
and the CreateString() is:
private string CreateString(List<string> list)
{
StringBuilder stringedData = new StringBuilder();
for (int i = 0; i < list.Count; i++)
{
stringedData.Append(list[i] + ", ");
}
return stringedData.ToString();
}
my intentions were to convert the "name" query to list and then sent it to CreateString() to convert it to one long concatenated string.
I tried using .Aggregate((current,next) => current + "," + next);
but when i try to convert my query to DataTable like below:
public DataTable ToDataTable(Object query)
{
DataTable dt = new DataTable();
IDbCommand cmd = dc.GetCommand(query as IQueryable);
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = (SqlCommand)cmd;
cmd.Connection.Open();
adapter.Fill(dt);
cmd.Connection.Close();
return dt;
}
I'm getting exception that "dc.GetCommand()" can't understand query with Aggregate method
later I tried to even use this simple query:
var resultTable = from itemin dc.table
select new
{
name = CreateString()
};
When CreateString() returns "success", nothing was inserted to "name"
why there is no way of using methods in select clause?
Thank you
Yotam
There is difference between LINQ to objects and LINQ to some-db-provider. Generally speaking, when using IQueryable, you can't use any methods, except the ones your provider understands.
What you can do is to retrieve the data from the database and then do the formatting using LINQ to objects:
var data = from item in dc.table
where /* some condition */
select item;
var result = from item in data.AsEnumerable()
select new
{
name = SomeFunction(item)
}
The AsEnumerable() extension method forces processing using LINQ to objects.
Forgive me if I've miss interpreted your question. It seems that what you are trying to do is abstract your select method for reuse. If this is the case, you may consider projection using a lambda expression. For example:
internal static class MyProjectors
{
internal static Expression<Func<Object1, ReturnObject>> StringDataProjector
{
get
{
return d => new Object1()
{
//assignment here
}
}
}
}
Now you can select your datasets as such:
dc.Table.Select(MyProjectors.StringDataProjector)
As for the concatenation logic, what about selecting to some base class with an IEnumerable<string> property and a read-only property to handle the concatenation of the string?

IQueryable to IQueryable<T>

is it possibile convert an IQueryable object to IQueryable where T is a mapped entity? (T will be a POCO class).
Thanks in advance.
Just Cast<T>() it. Assuming it is a queryable of the same type. Otherwise you could use the OfType<T>() filtering method to filter out items of a certain type.
IQueryable query = ...;
IQueryable<MyType> x = query.Cast<MyType>(); // assuming the queryable is of `MyType` objects
IQueryable<MyDerivedType> y = query.OfType<MyDerivedType>(); // filter out objects derived from `MyType` (`MyDerivedType`)
However in your case, you say that you are using Dynamic LINQ and doing a dynamic projection. Consider this completely made up query:
var query = dc.SomeTable
.Where("SomeProperty = \"foo\"")
.Select("new (SomeProperty, AnotherProperty)");
It results in a query of type IQueryable. You cannot cast this to a query of a specific type IQueryable<T> after all, what is T? What the Dynamic LINQ library does is creates a type that derives from DynamicCass. You could cast to IQueryable<DynamicClass> (query.Cast<DynamicClass>()) but you will not have access to the properties so it's moot.
Really the only nice option you have is to use dynamic to access these properties in this case.
foreach (dynamic x in query)
{
string someProperty = x.SomeProperty;
int anotherProperty = x.AnotherProperty;
// etc...
}
If you want to convert this to a query of your POCO objects, you'll have to do the conversion as a separate step but using LINQ to Objects.
IEnumerable<SomePoco> query =
dc.SomeTable
.Where("SomeProperty = \"foo\"")
.Select("new (SomeProperty, AnotherProperty)")
.Cast<DynamicObject>().AsEnumerable().Cast<dynamic>()
.Select(x => new SomePoco
{
SomeProperty = x.SomeProperty,
AnotherProperty = x.AnotherProperty,
});
If you must have an IQueryable<T>, then you should not use dynamic projections in the first place.
IQueryable<SomePoco> query =
dc.SomeTable
.Where("SomeProperty = \"foo\"")
.Select(x => new SomePoco
{
SomeProperty = x.SomeProperty,
AnotherProperty = x.AnotherProperty,
});
Seeing as how the cast is not working for LINQ to Entities, then I suppose the only option you have to get a strongly type collection of your POCO objects is to break this out into a loop.
var query = dc.SomeTable
.Where("SomeProperty = \"foo\"")
.Select("new (SomeProperty, AnotherProperty)");
var result = new List<SomePoco>();
foreach (dynamic x in query)
{
result.Add(new SomePoco
{
SomeProperty = x.SomeProperty,
AnotherProperty = x.AnotherProperty,
});
}

How to easly convert linq result to Business Object Collection <T>

I have Business Object Collection
I'd like to filter rows using linq, but noticed it returns IEnumerable what can not be cast then to my BOC
E.g I cannot do that
BOC <Client> bocCLients = (BOC <Client>)
from C in ClientsColl where C.ClientId == 100 select C
I've resolved that by looping by linq results and adding returned object to my original collection.
I wonder if there is simpler way?
var bocCLients = ClientsColl.Where(c => c.ClientId == 100).ToList();
Or
var bocCLients = new BOC<Client>(ClientsColl.Where(c => c.ClientId == 100));
Edit
Or maybe an AddRange extension
public static void AddRange<T>(this ICollection<T> colSource, IEnumerable<T> collection)
{
if (colSource is List<T>)
((List<T>)colSource).AddRange(collection); //If List use build in optimized AddRange function
else
{
foreach (var item in collection)
colSource.Add(item);
}
}
This looks like a perfect opportunity to create an extension method. From looking at your question, it appears that ClientsColl already contains objects of type Client. In this case, your solution of a foreach loop is ideal. However, you can encapsulate that solution into an extension method and make it reusable and easy to read.
Here's an example of how it would look like:
public static BOC<T> ToBOC<T>(this IEnumerable<T> sourceCollection)
{
var boc = new BOC<T>();
foreach (T item in sourceCollection)
{
boc.Add(item);
}
return boc;
}
Using this extension method, you would just write your query as follows:
BOC<Client> bocClients =
(
from C in ClientsColl
where C.ClientID == 100
select C
).ToBOC();
EDIT:
To follow up on the idea of the more generic extension method to ICollection, but keeping in line the original question which was to perform a sort of Cast to a specific type of collection, and now having the new information that BOC implements ICollection, here is a more generic extension method and usage to perform the job:
public static TCollection ToICollection<T, TCollection>(this IEnumerable<T> sourceCollection)
where TCollection : ICollection<T>, new()
{
TCollection col = new TCollection();
foreach (T item in sourceCollection)
{
col.Add(item);
}
return col;
}
And usage:
BOC<Client> bocClients2 =
(
from C in ClientsColl
where C.ClientID == 100
select C
).ToICollection<Client, BOC<Client>>();
Does this look more useful? Let me know what you think.

How to access data into IQueryable?

I have IQueryable object and I need to take the data inside the IQueryable to put it into Textboxs controls. Is this possible?
I try something like:
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.????
}
Update:
I'm doing this:
public IQueryable getData(String tableName, Hashtable myparams)
{
decimal id = 0;
if (myparams.ContainsKey("id") == true)
id = (decimal)myparams["id"];
Type myType= Type.GetType("ORM_Linq." + tableName + ", ORM_Linq");
return this.GetTable(tableName , "select * from Articu where id_tipo_p = '" + id + "'");
}
public IQueryable<T> GetTable<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
{
return _datacontext.GetTable<T>().Where(predicate);
}
This returns a {System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable1[ORM_Linq.Articu]}`
I don't see any method like you tell me. I see Cast<>, Expression, ToString...
EDIT: Updated based on additional info from your other posts...
Your getData method is returning IQueryable instead of a strongly typed result, which is why you end up casting it. Try changing it to:
public IQueryable<ORM_Linq.Articu> getData(...)
Are you trying to query for "Articu" from different tables?
With the above change in place, your code can be rewritten as follows:
ORM_Linq.Articu result = mydata.SingleOrDefault();
if (result != null)
{
TextBoxCode.Text = result.id.ToString();
TextBoxName.Text = result.descrip;
}
If you have a single result use SingleOrDefault which will return a default value if no results are returned:
var result = mydata.SingleOrDefault();
if (result != null)
{
textbox1.text = result.ProductName; // use the column name
}
else
{
// do something
}
If you have multiple results then loop over them:
foreach (var item in mydata)
{
string name = item.ProductName;
int id = item.ProductId;
// etc..
}
First, you should be using a strongly-typed version of IQueryable. Say that your objects are of type MyObject and that MyObject has a property called Name of type string. Then, first change the parameter mydata to be of type IQueryable<MyObject>:
public void setdata (IQueryable<MyObject> mydata)
Then we can write a body like so to actually get some data out of. Let's say that we just want the first result from the query:
public void setdata (IQueryable<MyObject> mydata) {
MyObject first = mydata.FirstOrDefault();
if(first != null) {
textbox1.Text = first.Name;
}
}
Or, if you want to concatenate all the names:
public void setdata(IQueryable<MyObject> mydata) {
string text = String.Join(", ", mydata.Select(x => x.Name).ToArray());
textbo1.Text = text;
}
Well, as the name suggests, an object implementing IQueryable is... Queryable! You'll need to write a linq query to get at the internal details of your IQueryable object. In your linq query you'll be able to pull out its data and assign bits of it where ever you'd like - like your text box.
Here's a great starting place for learning Linq.
I think you find the same mental struggle when coming from FoxPro and from DataSet. Really nice, powerful string-based capabilities(sql for query, access to tables and columns name) in these worlds are not available, but replaced with a compiled, strongly-typed set of capabilities.
This is very nice if you are statically defining the UI for search and results display against a data source known at compile time. Not so nice if you are trying to build a system which attaches to existing data sources known only at runtime and defined by configuration data.
If you expect only one value just call FirstOrDefault() method.
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.FirstOrDefault().PropertyName;
}

Resources