Mutate IQueryable after calling ODataQueryOptions.ApplyTo - c#

I'm passing an OData query into a method, but I need perform an additional join to the query to retrieve some related data, as we want to get it all in one round trip to the DB.
var products = query.ApplyTo(_repository.Query, ODataQuerySettings)
.Cast<Product>()
.ToList();
// Ideally, this will be the only query that's run.
var productImages = products.GroupJoin(_imageRepository.Query, p => p.ProductId, image => image.ProductId, (product, image) => new {
Product = product,
Image = image.DefaultIfEmpty()
}).ToList();
foreach (var item in productImages) {
...
}
This works, but is doing it with two queries. It seems that ApplyTo executes the query against the DB, rather than just providing you with a mutable IQueryable. If I remove the ToList() call from the first query, it throws an exception.
Is there any way to do this?

Related

How to get data from linq

I am trying to get data from linq in asp.net core. I have a table with a Position with a FacultyID field, how do I get it from the Position table with an existing userid. My query
var claimsIdentity = _httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier)?.Value.ToString();
var data = _context.Positions.Where(p => p.UserID.ToString() == userId).Select(x => x.FacultyID).???;
What can I add after the mark? to get the data. Thank you so much
There are several things you can do. An example in your case would be:
var data = _context.Positions.Where(p => p.UserID.ToString() == userId).Select(x => x.FacultyID).FirstOrDefault();
If you expect more than 1 results, then you would do:
var data = _context.Positions.Where(p => p.UserID.ToString() == userId).Select(x => x.FacultyID).ToList();
You have to be aware of the difference between a query and the result of a query.
The query does not represent the data itself, it represents the potential to fetch some data.
If you look closely to the LINQ methods, you will find there are two groups: the LINQ methods that return IQueryable<...> and the others.
The IQueryable methods don't execute the query. These functions are called lazy, they use deferred execution. You can find these terms in the remarks section of every LINQ method.
As long as you concatenate IQueryable LINQ methods, the query is not executed. It is not costly to concatenate LINQ methods in separate statements.
The query is executed as soon as you start enumerating the query. At its lowest level this is done using GetEnumerator and MoveNext / Current:
IQueryable<Customer> customers = ...; // Query not executed yet!
// execute the query and process the fetched data
using (IEnumerator<Customer> enumerator = customers.GetEnumerator())
{
while(enumerator.MoveNext())
{
// there is a Customer, it is in property Current:
Customer customer = enumerator.Current;
this.ProcessFetchedCustomer(customer);
}
}
This code, or something very similar is done when you use foreach, or one of the LINQ methods that don't return IQueryable<...>, like ToList, ToDictionary, FirstOrDefault, Sum, Any, ...
var data = dbContext.Positions
.Where(p => p.UserID.ToString() == userId)
.Select(x => x.FacultyID);
If you use your debugger, you will see that data is an IQueryable<Position>. You'll have to use one of the other LINQ methods to execute the query.
To get all Positions in the query:
List<Position> fetchedPositions result = data.ToList();
If you expect only one position:
Position fetchedPosition = data.FirstOrDefault();
If you want to know if there is any position at all:
if (positionAvailable = data.Any())
{
...
}
Be aware: if you use the IQueryable, the data will be fetched again from the DbContext. So if you want to do all three statements efficiently these, make sure you don't use the original data three times:
List<Position> fetchedPositions result = data.ToList();
Position firstPosition = fetchedPostion.FirstOrDefault();
if (firstPosition != null)
{
ProcessPosition(firstPosition);
}

Significane of using AsEnumerable() in query to take anonomous value in to view model

i just don't understand the meaning of writing AsEnumerable() in linq query.
i am having one UserDetail table in which i am fetching all details of user and i want to take
name,email in to my model.
var data = context.UserDetails
.Select(temp => new FullName = temp.Fullname, Email = temp.Email })
.ToList()
.AsEnumerable()
.Select(d => new UserDetailsModel { Fullname = d.FullName, Email = d.Email })
.ToList();
What is the meaning of AsEnumerable() as because when i am removing this there is no error but as i have seen some queries where they are using this method.
And does anybody have better approach for taking anonymous object value in to my View Model?
Does it improve performance? What is the significance of this method? Can anybody explain me this method in context to my question and details?
Your first select is querying the database and returns IQueryable<T>. .AsEnumerable() make it IEnumerable<T>. IQueryable<T> extends IEnumerable<T> so whatever you can do with IEnumerable<T>, you can also do with IQueryable<T>.
Another big difference is that IEnumerable<T> fetches the entire table and applies the query on that afterwards, whereas IQueryable<T> only fetches the filtered data.
Note that AsEnumerable() does not materialise the query yet. You often use .ToList() to materialize your query so other non sql statements can be performed on the result set as per this example
You can simplify your code to
var data = (from temp in context.UserDetails select temp)
.Select(d => new UserDetailsModel
{
Fullname = d.fullName,
Email = d.Email
}).ToList());
ToList() or AsEnumerable() work similarly, you don't need both. List is a IEnumerable + some extra features such as Add(), Count, etc., and the idea of using any of these is to materialize the query (IQueryable).
Also, You don't need the temp query, you could do a direct select:
var data = context.UserDetails
.Select(d => new UserDetailsModel {...})
.ToList()); // or AsEnumerable()

Entity Framework Exclude Fields Query Count and POCO best way

I created a qr object to get total item count without getting all data from database and also if necessary to select MyViewModel list
var qr = dbSet.Select(o =>
new { ParentID = o.Parent.ID, o.Parent.Name, o.ID})
.Select(o => new MyViewModel(o.ParentID, o.Name, o.ID));
But when I tried qr.Count() and qr.ToList() they all run same query in database which is for items not only for items' count.
What is the best and fastest way to get 'MyViewModel' items' itself or items' count or both at the same time?
The problem with this query is because it calls a constructor of MyViewModel in
.Select(o => new MyViewModel(o.ParentID, o.Name, o.ID))
This call effectively converts your IQueryable to IEnumerable, as it is not possible to call a constructor from a query.
To avoid this conversion, use object initializer instead of constructor:
.Select(o => new MyViewModel
{
ParentID = o.ParentID,
Name = o.Name,
ID = o.ID
})
You have .AsEnumerable() call in your linq query. That means that any translate-your-query-into-SQL stops right there, and anything after will not be translated to SQL. Therefore, the only query that will be run in your database is the retrieval of you complete dbSet. If you want to only retrieve the count, than run the following linq query
var qrCount = dbSet.Count();

Order query by int from an ordered list LINQ C#

So I am trying to order a query by an int var that is in an ordered list of the same int vars; e.g. the query must be sorted by the lists order of items. Each datacontext is from a different database which is the reason i'm making the first query into an ordered list of id's based on pet name order, only the pet id is available from the second query's data fields, Query looks like:
using (ListDataContext syndb = new ListDataContext())
{
using (QueryDataContext ledb = new QueryDataContext())
{
// Set the order of pets by name and make a list of the pet id's
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
// Reorder the SoldPets query using the ordered list of pet id's
var slp = ledb.SoldPets.OrderBy(x => stp.IndexOf(x.petId)).Select(x => x);
// do something with the query
}
}
The second query is giving me a "Method 'Int32 IndexOf(Int32)' has no supported translation to SQL." error, is there a way to do what I need?
LINQ to SQL (EF) has to translate your LINQ queries into SQL that can be executed against a SQL server. What the error is trying to say, is that the .NET method of IndexOf doesn't have a SQL equivalent. You may be best to get your data from your SoldPets table without doing the IndexOf part and then doing any remaining ordering away from LINQ to SQL (EF).
Something like this should work:
List<StoredPet> storedPets;
List<SoldPet> soldPets;
using (ListDataContext listDataContext = new ListDataContext())
{
using (QueryDataContext queryDataContext= new QueryDataContext())
{
storedPets =
listDataContext.StoredPets
.OrderBy(sp => sp.Name)
.Select(sp => sp.PetId)
.ToList();
soldPets =
queryDataContext.SoldPets
.ToList();
}
}
List<SoldPets> orderedSoldPets =
soldPets.OrderBy(sp => storedPets.IndexOf(sp.PetId))
Note: Your capitalisation of PetId changes in your example, so you may wish to look at that.
LinqToSql can't transalte your linq statement into SQL because there is no equivalent of IndexOf() method. You will have to execute the linq statement first with ToList() method and then do sorting in memory.
using (ListDataContext syndb = new ListDataContext())
using (QueryDataContext ledb = new QueryDataContext())
{
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
// Reorder the SoldPets query using the ordered list of pet id's
var slp = ledb.SoldPets.ToList().OrderBy(x => stp.IndexOf(x.petId));
}
You can use this, if the list size is acceptable:
using (ListDataContext syndb = new ListDataContext())
{
using (QueryDataContext ledb = new QueryDataContext())
{
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
var slp = ledb.SoldPets.ToList().OrderBy(x => stp.IndexOf(x.petId));
// do something with the query
}
}

Using Count with Take with LINQ

Is there a way to get the whole count when using the Take operator?
You can do both.
IEnumerable<T> query = ...complicated query;
int c = query.Count();
query = query.Take(n);
Just execute the count before the take. this will cause the query to be executed twice, but i believe that that is unavoidable.
if this is in a Linq2SQL context, as your comment implies then this will in fact query the database twice. As far as lazy loading goes though it will depend on how the result of the query is actually used.
For example: if you have two tables say Product and ProductVersion where each Product has multiple ProductVersions associated via a foreign key.
if this is your query:
var query = db.Products.Where(p => complicated condition).OrderBy(p => p.Name).ThenBy(...).Select(p => p);
where you are just selecting Products but after executing the query:
var results = query.ToList();//forces query execution
results[0].ProductVersions;//<-- Lazy loading occurs
if you reference any foreign key or related object that was not part of the original query then it will be lazy loaded in. In your case, the count will not cause any lazy loading because it is simply returning an int. but depending on what you actually do with the result of the Take() you may or may not have Lazy loading occur. Sometimes it can be difficult to tell if you have LazyLoading ocurring, to check you should log your queries using the DataContext.Log property.
The easiest way would be to just do a Count of the query, and then do Take:
var q = ...;
var count = q.Count();
var result = q.Take(...);
It is possible to do this in a single Linq-to-SQL query (where only one SQL statement will be executed). The generated SQL does look unpleasant though, so your performance may vary.
If this is your query:
IQueryable<Person> yourQuery = People
.Where(x => /* complicated query .. */);
You can append the following to it:
var result = yourQuery
.GroupBy (x => true) // This will match all of the rows from your query ..
.Select (g => new {
// .. so 'g', the group, will then contain all of the rows from your query.
CountAll = g.Count(),
TakeFive = g.Take(5),
// We could also query for a max value.
MaxAgeFromAll = g.Max(x => x.PersonAge)
})
.FirstOrDefault();
Which will let you access your data like so:
// Check that result is not null before access.
// If there are no records to find, then 'result' will return null (because of the grouping)
if(result != null) {
var count = result.CountAll;
var firstFiveRows = result.TakeFive;
var maxPersonAge = result.MaxAgeFromAll;
}

Categories