LINQ data services "Odata" select first child from parent - c#

I have an odata service that returns a parent with a child collection. Something like Order.OrderDetails.
On the client I have tried:
From x in context.Order.Expand("OrderDetails") Select x.OrderDetails[0]
"Error translating Linq expression to URI: The method 'Select' is not supported."
From x in context.Order.Expand("OrderDetails") Select x.OrderDetails.FirstOrDefault()
"Error translating Linq expression to URI: The method 'Select' is not supported."
From x in context.Order Select x.OrderDetails.FirstOrDefault()
"Error translating Linq expression to URI: The method 'Select' is not supported."

It seems to me that aggregating (in your case FirstOrDefault) on navigation properties is not currently supported in OData except for the special cases of all and any. You either need to retrieve all of the OrderDetails (i.e. just do Expand and do a FirstOrDefault on the client) which may or may not be acceptable depending on the amount of data and your performance requirements or create a specific service operation method on the server to retrieve exactly the data you require. It seems that support for aggregation is coming in OData 4. There is a specification dated 26.02.2014 that defines aggregation in OData - http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326290 but I don't know if current tools support it yet.
To implement the first approach with expand you need to do something like this
var result = (from order in context.Orders.Expand(o=>o.OrderDetails)
where...
orderby...
select order).Skip(...).Take(...) //if you need paging
.ToList();
var actualResult = from o in result
select new { Order = o, FirstDetail = o.FirstOrDefault() };
Of course depending on your needs you may not get the first detail in this way. The point is that you execute the service query (.ToList()) before dealing with the projection.

Related

C# - filtering collection based on another collection exception

I've searched through numerous examples on how to filter collection based on another collection and I have found an easy way, like so:
var oShipnotes = await DbContext.ShipNotes.Where(s => oManageRouteDto.ShipNotes.Any(mr => mr.Id == s.Id)).ToListAsync();
however it throws an exception that says it cannot be translated to SQL query.
Could anyone point me the right direction how to solve this?
Thanks!
Replace nested LINQ query to materialized list of identifiers:
// 1) get the list of target ship note identifiers
var ids = oManageRouteDto.ShipNotes.Select(mr => mr.Id).ToList();
// 2) pass this list into Where using Contains
var oShipnotes = await DbContext.ShipNotes.Where(s => ids.Contains(s.Id)).ToListAsync();
EF is aware of this pattern and translates IList<T>.Contains into SQL's IN condition.
Since EF deals with IQueryables, each LINQ query must be translated into valid SQL expression. As a result, EF and underlying provider cannot translate every valid LINQ query (from C# perspective) just because SQL is not C#.

MongoDb select many with a join on the result

I am trying to perform a join on a collection from the results of a select many using the MongoDb Linq driver
pseudo code is
from x in something
from y in x.subcollection
join a in detailed on y.id equals a.id
select new
{
x.field,
x.someotherfield,
a.somedetail,
a.someotherdetail
}
unfortunately this syntax gives me
System.NotSupportedException : $project or $group does not support {document}.
I would be open to doing this via the aggregate pipeline fluent library as well, if its not possible using the linq provider, but I have no idea of the syntax, the documentation is ... lacking ...
I assume it would be an unwind, a lookup and a projection - but how?
-------Edit-------
Getting closer, but still no cigar...
So the unwind seems to work fine, the lookup also works fine, the issue is trying to work with the resulting BsonDocument in the projection stage
I know that there are some typed overloads where I could supply a type - but it will be impractical to create intermediate types for every stage of every pipeline.
My issues are.
I seem to have to hard cast the BsonValue's rather than use the
AsSomething methods, which seems weird (bug?)
The joined collection is
included as an Array (as expected) in my case it will always have 1
record, I can get the First ok as a BSonDocument, but I cannot drill
further into it (the ["FirstName"] call fails with "No matching
creator found"
Please help !

Perform OrderBy on the results of Apply with Aggregate OData Version 4

Consider I have an odata query like this:
Sessions?$apply=filter(SomeColumn eq 1)/groupby((Application/Name), aggregate(TotalLaunchesCount with sum as Total))
Sessions and Application entities are linked by ApplicationId. I want to apply orderby on "Total" and get top 5 results as odata query response.
I tried adding &$top=5 at the end of the above mentioned query. Its says:
The query specified in the URI is not valid. Could not find a property named 'Total' on type 'Sessions'.
Can anyone tell me if such query is supported?
It is supported. $skip, $top, $orderby are "performed" on result from $apply chain.
In this case query should looks like this:
Sessions?$apply=filter(SomeColumn eq 1)
/groupby((Application/Name), aggregate(TotalLaunchesCount with sum as Total))
&$orderby=Total
&$top=5
3.15 Evaluating $apply

Union IQueryable and List

This is my code:
var query = context.SomeEntities.Select(e => new SomeDto
{
Name = e.Title,
})
The variable query is IQueryable.
var list = new List<SomeDto>
{
new SomeDto
{
Name = "SomeName1"
},
new SomeDto
{
Name = "SomeName2"
}
};
Now I need union these collections,I write
query = query.Union(list)
But in this place I get exception
Unable to create a constant value of type 'SomeDto'. Only primitive types or enumeration types are supported in this context
I know that I can do ToList() for first collection,but I need union collections without call to database.How can I do this?
As user3825493 pointed, there are answers regarding similar issues. The first link pretty much explains the whole thing.
Your problem is that due to the fact that Union is mapped to SQL UNION, arguments of the operation are to be converted to data types acceptable by Entity Framework. However, the only types acceptable by it for items inside IEnumerable input parameters are, as specified in the error you get, primitive types and enum's.
You should either:
var unionOfQueryWithList = query.AsEnumerable().Union(list)
or, if you like to use the SQL facilities, write a stored procedure which performs the union as part of its logic and find the way to pass your data into SQL, such as this.
The former loads all the results of the query into a memory before performing the union. It is convenient for the cases when you have all the pre-filtered data returned from EF and only want to use the output for later processing in your code.
The later takes you out of the EF zone and required for the cases when you have operations which will benefit from running on SQL engine, such as filtering or joining DB data based on a complex data you have access to in your code.
either you convert the query to IEnumerable or you can try this :
list.Union(query); // use Union() of list not query :)
it will work and you still don't need to get data from DB you can reverse the order if you want :)

LINQ to Entities does not recognize the method 'System.String ToString(Int32)' method, and this method cannot be translated into a store expression

Using MVC3 VS2010 and SQL Server 2008 Express
I am trying to filter based on two SQL Server tables and display the result.
One table is clients table and the other is agent. They have in common ClientAgentID in the clients table and ID in the Agents table. An agent logs and should be able to see the clients assigned to the agent. If you have any ideas on the best way to do this please help me. So far I am trying to filter in the clients controller and here is what I have but the message is I am getting is in the title.
public ActionResult Index()
{
//This displays all the clients not filtered by the Agent ID number
//var clientItems = db.MVCInternetApplicationPkg;
//return View(clientItems.ToList());
//Trying to filter by the agent name given in the login page then finding
//the agent ID
var getAgentID = from a in db.AgentsPkg
where a.AgentLogin == User.Identity.Name
select a.ID;
var clientItems = from r in db.MVCInternetApplicationPkg
where Convert.ToString(r.ClientAgentID)
== Convert.ToString(getAgentID)
select r;
//THIS IS THE LINE OF CODE THAT SHOWS THE ERROR MESSAGE
return View(clientItems.ToList());
}
This is my first MVC project after the Music Store so am willing to learn and accept any help or advice.
Cheers
Here is the solution that I used in the end. Any feed back on if this is a good approach would be appreciated
public ActionResult Index()
{
var innerJoint = from agents in db.AgentsPkg where agents.AgentLogin == User.Identity.Name
join clients in db.MVCInternetApplicationPkg on agents.ID equals clients.ClientAgentID
select clients;
return View(innerJoint.ToList());
}
you do not want to use the Convert in your linq statement!!!!!!
string clientagentid=Convert.ToString(r.ClientAgentID);
string getagentid= Convert.ToString(getAgentID);
var clientItems = (from r in db.MVCInternetApplicationPkg
where clientagentid==getagentid
select r).ToList();
return View(clientItems);
1. Reason for the error:
As others have stated, it's due to the use of Convert.ToString() within your where clause, which Linq cannot convert into SQL. I would expect your original query to work just by removing the two Convert.ToString() functions.
2. "....best way to do this":
Well, a better way.... :)
In Entity Framework, the easy way to navigate between related entities is via Navigation Properties. If your approach is "Database First", these should be generated for you in your EDMX. If your approach is "Code First", there's a good post here describing how to set this up.
Either way, I'd expect your Client class to have a navigation property to Agent (i.e. similar to OrderDetail's Order property in the MvcMusicStore sample you mention):
public virtual Agents Agent { get; set; }
Then your method becomes very simple (i.e. similar to many of the controller methods in MvcMusicStore) ...no Joins or multiple statements required:
var clients = db.MVCInternetApplicationPkg.Where(c => c.Agent.AgentLogin == User.Identity.Name);
return View(clients.ToList());
The answer is to use SqlFunctions.StringConvert (use 'decimal' or 'double' but 'int' won't work) see example
using System.Data.Objects.SqlClient ;
var clientItems = from r in db.MVCInternetApplicationPkg
where SqlFunctions.StringConvert((double ), r.ClientAgentID)
== SqlFunctions.StringConvert((decimal) , getAgentID)
select r;
see http://msdn.microsoft.com/en-us/library/dd466166.aspx for more info.
What is going on is the Linq provider (Linq to Entities) is trying to convert your query to SQL, and there is no mapping for Convert. These errors are sometimes hard to decipher, but the clue is in the "String.ToString()" line. Also realize that because of deferred execution, the error won't show up until the clientItems is iterated, in your case with the call to toList() in return View(clientItems.ToList());
LINQ cannot map Convert.ToInt32() into equivalant T-SQL, so it throws exception. As COLD TOLD said you have to convert the value and then use the converted value in the query.
please check : Why do LINQ to Entities does not recognize certain Methods?

Categories