Implementing service queryables: IQueryable to IEnumerable and back - linq

In RIA Services (and probably other frameworks such as WCF Data Services) one can implement data services methods by supplying methods returning an IQuerably. For example:
IQueryable<Customer> GetCustomers()
{
return this.DbContext.Customers.Where(c => !c.IsDeleted);
}
A query from the client can then supply an additional filter (among other things). Both the client-provided filter and the filter for returning only undeleted customers will be combined an sent to the database in SQL by entity framework (or whatever ORM is used).
Often entity framework isn't powerful enough to express the query one wants to write, in which case we have to go to linq to objects:
IQueryable<CustomerWithFluff> GetCustomers()
{
var customers =
this.DbContext.Customers
.Include("FluffBits")
.Where(c => !c.IsDeleted)
.ToList();
return
from c in customers
select new CustomerWithFluff()
{
CustomerName = c.Name,
ComplexFluff = String.Join(", ", from b in c.FluffBits select b.FluffText)
};
}
The sql to the database will now still contain the restriction on "IsDeleted", but not any further filter provided by a client: Those will be applied on the linq to objects level after all the data has already been fetched.
If I don't care to let the client filter on any of the data that I need to compose with linq-to-objects (only "ComplexFluff" in this example), is there a way to still allow filtering on the properties that are simply projected (only "CustomerName" in this example)?

Yes there is, however is not as simple as one can expect. You have to override the
public override IEnumerable Query(QueryDescription queryDescription, out IEnumerable<ValidationResult> validationErrors, out int totalCount)
method, where you can get, in queryDescription.Query the actual query and Expression that is going to be executed against your Queryable (that is, what is being returned by queryDescription.Method.Invoke(this, queryDescription.ParamterValues)).
You can then get the expression sent by the client and pass it to your method (maybe as a reference to some sort of field in you domainService, don't forget that WCF Ria Services are instantianted at each call, i.e. like a regular wcf perCall instancing model) where you'll have to combine
var customers =
this.DbContext.Customers
.Include("FluffBits")
.Where(c => !c.IsDeleted)
prior to the "ToList()" method call.
Not easy, but quite possible

Related

Linq to entities custom function in select

In Linq to Entities one can't use standard c# method to modify the results in the "select" clause, canonical functions are required.
I need to invoke such query:
CoreEntities.Contracts.Select(
c=> new {
c.Name,
c.Type,
Role = MapToRole(c));
private string MapToRole(Contract contract) {
switch(contract.Type) {
case: CTypes.Main: return "somerole1";break;
case: CTypes.Secondary: return "somerole2";break;
// ...
default: break;
}
return "none";
}
The "MapToRole" is a C# method created just to declutter the linq query.
Is there a way to create a custom c# function that would be accepted by Entity Framework "linq to Entity" parser?
I've found a solution for query filters but not for data formatting.
It appears that as it's a simple transformation, there's no reason this needs to be translated by the provider. I would suggest simply adding AsEnumerable() before your Select() to "detach" your projection from the provider.
Alternatively you could have a CTypes property in your data type and a method/property that performs the transformation of that within your model.
As a side note, doing this particular transformation in your application layer means that you're only pulling through the enum value, not a string - therefore less data from the provider.

Dataset conversion to Linq object

Our WCF services return Datasets to the webserver,
In the new .NET web site we are going to use MVC 5. Since MVC 5 framework works really well with known business objets (validation framework etc.), we need to convert Datasets to known business objects in the Model classes.
We tried following conversion,
public List<Category> GetCategories()
{
List<Category> cats = new List<Category>();
DataTable dt = //get data table from the dataset;
foreach (DataRow row in dt.Rows)
{
Category cat = new Category();
cat.CategoryID = int.Parse(row["CategoryID"].ToString());
cat.CategoryName = row["CategoryName"].ToString();
cat.Description = row["Description"].ToString();
cat.Picture = GetBytes(row["Picture"].ToString());
cats.Add(cat);
}
return cats;
}
Assume that we retrieve and unpack the data table.
Will this be an expensive conversion if there are 100s' of request per second accessing this code block?
What would be a better way to test the performance under load?
Or is there a better approach to solve this problem?
Really appreciate any help on this.
I would not worry about the time it takes to convert the DataSet to domain objects since it's going to be a fraction of the remote call to get the data through a remote service.
However, for result sets that rarely change I would recommend adding caching around the resolved domain objects to avoid the conversion, but more importantly avoid the WCF call and subsequent DB call.

Using IQueryable Or IEnumerable inside my search action method

i have the following action method:-
[AcceptVerbs(HttpVerbs.Post)]
public PartialViewResult Search(string q, int classid)
{
var users = r.searchusers(q, classid);
// code does here..............
which calls the following model repository method:-
public IQueryable<User> searchusers(string q, int id)
{
return from u in entities1.Users
where (!u.Users_Classes.Any(c => c.ClassID == id) && (u.UserID.Contains(q))
select u;
}
now if i change the IQueryable to IEnumerable as follow , will there be any changes on how the query will be executed in this case ?:-
public IEnumerable<User> searchusers(string q, int id)
{
return from u in entities1.Users
where (!u.Users_Classes.Any(c => c.ClassID == id) && (u.UserID.Contains(q))
select u;
}
Yes, in my testing once you cast to IEnumerable, that determines the query SQL. Any additional query composition you do after that will be done in memory after the query is executed.
So suppose you have a base query that loads a list of users and returns an IEnumerable. Then before you actually run through that list (thereby executing the query), you also add a .Where(i=>i.username='bob'). In that case, it will execute the whole select, and then apply a LINQ-to-Objects in memory filter for the "where username='bob'" part, which is probably not what you want, instead you want the whole thing to be run as part of the SQL statement.
So yes, always use IQueryable whenever you can so that your fully composed are run at once.
Yes it will change the query, you want to use the IQuerable for anything that uses a remote datasource.
In your case it will force linq to execute the query, where as IQuerable would wait until someone else to execute the query. IQuerable allows them, if they desire, to append more conditions to push down to the database for execution.
I generally enforce IEnumerable at the layer boundary, places where I dont want people modifying the queries that the system is going to generate.

Consuming Service Operations of an ADO.NET Data Service from a .NET Client

I am trying to build an ADO.NET Data Service with lots of entities and a few service operations. On one side I created a ASP.NET Web Application, in which an ADO.NET Entity Data Model and an ADO.NET Data Service are located. On the other side I created a second ASP.NET Web Application that has a Service Reference to the Data Service.
Entities are coming through very well, I can use LINQ to retrieve the data I want:
TestEntities entities = new TestEntities(
new Uri("http://localhost/service/service.svc"));
var query = from customer in entities.Customers
where customer.ID == 1234
select customer;
query.ToList();
This works. However, retrieving information through Service Operations completely eludes me.
Data Service-side code:
public static void InitializeService(IDataServiceConfiguration config) {
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
}
[WebInvoke]
public IQueryable<Customer> GetSomeCustomers() {
TestEntities entities = new TestEntities();
return from customer in entities.Customers
where customer.ID > 0 && customer.ID < 20
select customer;
}
When I added the Service reference to my client project, Visual Studio didn't pick up on any Service Operations. I know I can access them through constructed URIs and the BeginExecute method of either the DataServiceContext object or the TestEntities object (in this case), or something like that, but that is not how I want it.
What I want is to use LINQ to go through the returned data of the Service Operation.
Is this possible? It should be, right?
Simple stuff once you know.
Just a few things to know:
Currently DataServiceClientGenerator (which uses the EntityClassGenerator) doesnt create methods for the service operations.
Using CreateQuery method on the context is not supported for service operations, currently they work because there is no validation on the client side for that (you will notice that if you use CreateQuery the "()" is added to the end of the Query Method like this "http://localhost/service.svc/method()?parameter=2", you can use CreateQuery but it is not recommended.
Not all Service operations return values, but for this example i will only show an example for the ones that do.
public partial class NorthwindEntities
{
public IQueryable<Order> OrdersByRegion(int regionId)
{
return this.Execute<Orders>(new Uri(string.Format("{0}OrdersByCountry?regionId={1}", this.BaseUri, regionId), UriKind.RelativeOrAbsolute));
}
}
If you require more information please feel free to ask any questions.
PS.: On your example you dont need to create a new data context on your service operation (server side) the DataService has already a reference instantiated when the service is called.
You can actually override the create of the data context on the service side like this:
protected override NorthwindEntities CreateDataSource()
{
return new NorthwindEntities();
}

Stack overflow in LINQ to SQL and the Contains keyword

I have an Extension method which is supposed to filter a Queryable object (IQueryable) based upon a collection of Ids....
Note that IQueryable is sourced from my database via a LinqToSql request
public static IQueryable<NewsItemSummary> WithID(this IQueryable<NewsItemSummary> qry, IQueryable<Guid> Ids)
{
return from newsItemSummary in qry
where Ids.Contains(newsItemSummary.ID)
select newsItemSummary;
}
If Ids are created from an array or list and passed in as a queryable list, it DOESNT work
For example...
GetNewsItemSummary().WithID(ids.AsQueryable<Guid>())
If Ids is composed form a LinqToSql request, it DOES work!!
This is known issue:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=355026
My Ids collection cannot come from a LinqToSql request...
Note, if I change the function so that it consumes and IList rather than an IQueryable....
public static IQueryable<NewsItemSummary> WithID(this IQueryable<NewsItemSummary> qry, IList<Guid> Ids)
{
return from newsItemSummary in qry
where Ids.Contains(newsItemSummary.ID)
select newsItemSummary;
}
I now get the following exception:
Method 'Boolean Contains(System.Guid)' has no supported translation to SQL.
So... all I want to do is filter my collection of news based upon a list or array of Guids.... Ideas???
This will translate.
public static IQueryable<NewsItemSummary> WithID(
this IQueryable<NewsItemSummary> qry,
List<Guid> Ids
)
{
return from newsItemSummary in qry
where Ids.Contains(newsItemSummary.ID)
select newsItemSummary;
}
)
Translation of the Contains method against local collections was one of the last features added in the development of linq to sql for .net 3.5, so there are some cases that you would expect work that don't - such as translation of IList<T>.
Also, be aware that while LinqToSql will happily translate lists containing a vast number of items (I've seen it do over 50,000 elements), SQL Server will only accept 2,100 parameters for a single query.

Resources