Alternative way to do FirstOrDefault in LINQ - mysql

I've got a membership table that records whether a user is a member of a list. When an update to a user's membership occurs a new record is written and the previous records are left as is which allows a history of their memberships to be maintained. To get a user's membership status involves selecting their most recent entry.
An example of some user list membership data is below. The aim is to find a LINQ expression that groups by list and user but only returns the row with most recently inserted record.
List Name, Username, Comment, ExpiresOn, Inserted
Test List, joeb, second update, 2012-03-13 16:55:03, 2012-01-31 22:28:40
Test List, joeb, first update, 2012-02-13 16:55:01, 2012-01-31 22:28:39
Test List, joeb, initial, 2012-01-13 16:55:02, 2012-01-31 22:28:38
An SQL query illustrates how the current list membership status can be extracted.
select ulm2.ID, ulm2.ExpiresOn, ulm2.Comment, ulm2.Inserted
from UserListMembership as ulm1
left outer join UserListMembership ulm2 on ulm1.id = ulm2.id
group by ulm1.userlistid, ulm1.userid;
The question is how to write a LINQ expression for the query that doesn't use a nested FirstOrDefault call which causes my MySQL entity framework provider to throw a "System.NotSupportedException: Specified method is not supported." exception?
Update:
Below is my failing LINQ expression, it throws the "Specified method is not supported" once the FirstOrDefault call is added.
var query = from mem in db.UserListMemberships
where
mem.User.UserUsernames.Any(y => y.Username.ToLower() == username.ToLower())
&& mem.UserList.Account.Subscriptions.Any(x => x.ID == subscriptionID)
&& mem.ExpiresOn > utcNow
group mem by new { mem.UserListID, mem.UserID } into g
select new { UserListMembership = (from mem2 in db.UserListMemberships where mem2.UserListID == g.Key.UserListID && mem2.UserID == g.Key.UserID orderby mem2.Inserted descending select mem2).FirstOrDefault() };
return query.Select(a => a.UserListMembership).ToList();

Without posting code it's hard for anyone to see what your issue is.
Are you not able to just order the results by your date field and then select the first record? Something like users.OrderByDescending(u => u.Inserted).FirstOrDefault()?

Related

Selecting multiple columns and set it to list of DTOs

I want to get multiple columns from database in a single query and set it to the corresponding DTO object fields.
Error message:
java.lang.IllegalStateException: No data type for node:
org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'payment' {originalText=payment}
Query:
TypedQuery<Object[]> query = entityManager.createQuery("SELECT
payment, createdOn,responseMessage FROM PaymentLog log WHERE log.id
=:personId", Object[].class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<Object[]> results = query.getResultList();
for (Object[] log : results) {
paymentTransaction.setAmount(log[0].toString());
paymentTransaction.setDate(log[1].toString());
paymentTransaction.setDescription(log[2].toString());
transactionList.add(paymentTransaction);
}
P.S. I know I can use JPA constructor expression. But as I have to add the DTOs in a list of DTO(i.e. transactionList), so is there a way with JPA construction expression where I can do that by running the query only one time instead in a loop for every single DTO?
You can have the JPA provider transform the result set for you by means of a constructor expression:
http://www.objectdb.com/java/jpa/query/jpql/select#Result_Classes_Constructor_Expressions_
https://en.wikibooks.org/wiki/Java_Persistence/JPQL#Constructors
This requires that the specified class has a constructor matching the select expression. This would then look something like the below:
TypedQuery<PaymentTransaction> query = entityManager.createQuery("SELECT new PaymentTransaction (log.payment, log.createdOn, log.responseMessage ) FROM PaymentLog log WHERE log.id
=:personId", PaymentTransaction.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentTransaction> results = query.getResultList();
In JPA 2.1 you can also so like the below:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#ConstructorResult_.28JPA_2.1.29
What you could do is:
TypedQuery<PaymentLog> query = entityManager.createQuery("SELECT log FROM PaymentLog log WHERE log.id =:personId", PaymentLog.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentLog> results = query.getResultList();
for (PaymentLog log : results) {
paymentTransaction.setAmount(log.getPayment());
paymentTransaction.setDate(log.getCreatedOn());
paymentTransaction.setDescription(log.getResponseMessage());
transactionList.add(paymentTransaction);
}
It is not a good idea to select everything from the database if you are not going to use it. If the selected fields were the only columns in the table then approach above works.
If you had a lot more columns in the table, the previous would still work, but this might be better:
TypedQuery<PaymentTransaction> query = entityManager.createQuery("SELECT new PaymentTransaction (log.payment, log.createdOn, log.responseMessage) FROM PaymentLog log WHERE log.id =:personId", PaymentTransaction.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentTransaction> results = query.getResultList();
The above query will return an already created list of PaymentTransactions. You have to note that the class PaymentTransactionshould have a constructor that accept these fields in the given order. Otherwise it will cause an exception

Linq to EF not returning all data

I have following query against EF whereby mysql was used:
var query = from r in context.myContext
where r.clmn1.CompareTo("2015-11-19 00:00:00") > 0)
orderby r.someColumn
select r;
return query;
The number of returned rows is as expected. however some values of the property r.clmn2 repeat itself in the result of the query. For example I could not find clmn2 == 220011 because it was "overwritten" by the value 220033 (The value 220033 is correct and expected but should not "overwrite" other values). Strangely enough, when I add this condition to the query I get it in the result (of course then only and only this value) which means that the first condition is also valid for clmn2:
var query = from r in context.myContext
where r.clmn1.CompareTo("2015-11-19 00:00:00") > 0) && r.clmn2.Equals("220011")
orderby r.someColumn
select r;
return query;
The same query (the first one) works at DB-level and returns all values (will not be overwritten)
SELECT * FROM myContext.myTable
WHERE r.clmn1 > ("2015-11-19 00:00:00")
ORDER BY r.someColumn
It should be a problem of EF. I hope someone could help me!
Thanks in Advance.
I have prefixed the column/property clmn2 with [key] atribute in the generated entity class so that it is now a part of the multiple key, i.e., with other columns/properties. It works and i get all values from DB. Maybe cus this property comes from a DB-view, Visual Studio could not recognize it as a primary key as done by other properties.

How to update a number of Rows in a Table using Linq

I am using the following code to update the UserSession column of the Activities. Following code return the records if the ExpiryTimeStamp is less then current date.
Then it Update the UserSession column to 0 for the returned recods in the table.
.Now I wants that if there are 100 records are returned then these should update at one time instead of using the FoREach. Is it posible in Linq
CacheDataDataContext db = new CacheDataDataContext();
var data = (from p in db.Activities
where p.ExpiryTimeStamp < DateTime.Now
select p).ToList();
data.ForEach(ta => ta.UserSession = "0");
db.SubmitChanges();
In short, no: Linq-2-sql does not do batch updates out of the box.
(I am not sure your foreach will work like you wrote - i do not think so - but this is similar and will work)
foreach (var x in data)
{x.UserSession = "0";}
db.SubmitChanges()
BUT, even if you do it like this, Linq-2-sql will send an update statement for each record to the database. So with your example of 100 records returned you will get 100 individual updates send to the database.

Could not format node 'Value' for execution as SQL

I've stumbled upon a very strange LINQ to SQL behaviour / bug, that I just can't understand.
Let's take the following tables as an example: Customers -> Orders -> Details.
Each table is a subtable of the previous table, with a regular Primary-Foreign key relationship (1 to many).
If I execute the follow query:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Then I get an exception: Could not format node 'Value' for execution as SQL.
But the following queries do not throw an exception:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).OrderDateTime;
var q = from c in context.Customers
select (new Order()).Details.Count();
If I change my primary query as follows, I don't get an exception:
var q = from r in context.Customers.ToList()
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Now I could understand that the last query works, because of the following logic:
Since there is no mapping of "new Order()" to SQL (I'm guessing here), I need to work on a local list instead.
But what I can't understand is why do the other two queries work?!?
I could potentially accept working with the "local" version of context.Customers.ToList(), but how to speed up the query?
For instance in the last query example, I'm pretty sure that each select will cause a new SQL query to be executed to retrieve the Orders. Now I could avoid lazy loading by using DataLoadOptions, but then I would be retrieving thousands of Order rows for no reason what so ever (I only need the first row)...
If I could execute the entire query in one SQL statement as I would like (my first query example), then the SQL engine itself would be smart enough to only retrieve one Order row for each Customer...
Is there perhaps a way to rewrite my original query in such a way that it will work as intended and be executed in one swoop by the SQL server?
EDIT:
(longer answer for Arturo)
The queries I provided are purely for example purposes. I know they are pointless in their own right, I just wanted to show a simplistic example.
The reason your example works is because you have avoided using "new Order()" all together. If I slightly modify your query to still use it, then I still get an exception:
var results = from e in (from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, Count = (e.FirstOrder != null ? e.FirstOrder : new Order()).Details().Count() }
Although this time the exception is slightly different - Could not format node 'ClientQuery' for execution as SQL.
If I use the ?? syntax instead of (x ? y : z) in that query, I get the same exception as I originaly got.
In my real-life query I don't need Count(), I need to select a couple of properties from the last table (which in my previous examples would be Details). Essentially I need to merge values of all the rows in each table. Inorder to give a more hefty example I'll first have to restate my tabels:
Models -> ModelCategoryVariations <- CategoryVariations -> CategoryVariationItems -> ModelModuleCategoryVariationItemAmounts -> ModelModuleCategoryVariationItemAmountValueChanges
The -> sign represents a 1 -> many relationship. Do notice that there is one sign that is the other way round...
My real query would go something like this:
var q = from m in context.Models
from mcv in m.ModelCategoryVariations
... // select some more tables
select new
{
ModelId = m.Id,
ModelName = m.Name,
CategoryVariationName = mcv.CategoryVariation.Name,
..., // values from other tables
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia = cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId) ?? new ModelModuleCategoryVariationItemAmount()
select new
{
cvi.Id,
Amount = (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.FirstOrDefault() ?? new ModelModuleCategoryVariationItemAmountValueChange()).Amount
... // select some more properties
}
}
This query blows up at the line let mmcvia =.
If I recall correctly, by using let mmcvia = new ModelModuleCategoryVariationItemAmount(), the query would blow up at the next ?? operand, which is at Amount =.
If I start the query with from m in context.Models.ToList() then everything works...
Why are you looking into only the individual count without selecting anything related to the customer.
You can do the following.
var results = from e in
(from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, DetailCount = e.FirstOrder != null ? e.FirstOrder.Details.Count() : 0 };
EDIT:
OK, I think you are over complicating your query.
The problem is that you are using the new WhateverObject() in your query, T-SQL doesnt know anyting about that; T-SQL knows about records in your hard drive, your are throwing something that doesn't exist. Only C# knows about that. DON'T USE new IN YOUR QUERIES OTHER THAN IN THE OUTER MOST SELECT STATEMENT because that is what C# will receive, and C# knows about creating new instances of objects.
Of course is going to work if you use ToList() method, but performance is affected because now you have your application host and sql server working together to give you the results and it might take many calls to your database instead of one.
Try this instead:
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia =
cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(
mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId)
select new
{
cvi.Id,
Amount = mmcvia != null ?
(mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.Select(
x => x.Amount).FirstOrDefault() : 0
... // select some more properties
}
Using the Select() method allows you to get the first Amount or its default value. I used "0" as an example only, I dont know what is your default value for Amount.

Working around LinqToSQls "queries with local collections are not supported" exception

So, I'm trying to return a collection of People whose ID is contained within a locally created collection of ids ( IQueryable)
When I specify "locally created collection", I mean that the Ids collection hasnt come from a LinqToSql query and has been programatically created (based upon user input).
My query looks like this:
var qry = from p in DBContext.People
where Ids.Contains(p.ID)
select p.ID;
This causes the following exception...
"queries with local collections are not supported"
How can I find all the People with an id that is contained within my locally created Ids collection?
Is it possible using LinqToSql?
If Ids is a List, array or similar, L2S will translate into a contains.
If Ids is a IQueryable, just turn it into a list before using it in the query. E.g.:
List<int> listOfIDs = IDs.ToList();
var query =
from st in dc.SomeTable
where listOfIDs.Contains(st.ID)
select .....
I was struggling with this problem also. Solved my problem with using Any() instead
people.Where(x => ids.Any(id => id == x.ID))
As the guys mentioned above, converting the ids, which is of type IQueryable to List or Array will solve the issue, this will be translated to "IN" operator in SQL.But be careful because if the count of ids >= 2100 this will cause another issue which is "The server supports a maximum of 2100 parameters" and that is the maximum number of parameters(values) you can pass to "IN" in SQL server.
Another alternative would be keeping ids as IQueryable and using LINQ "Any" operator instead of "Contains", this will be translated to "EXISTS" in SQL server.
I'm sorry but the answers here didn't work for me as I'm doing dynamic types further along.
What I did was to use "UNION" in a loop which works great. Here's how:
var firstID = cityList.First().id;
var cities = dc.zs_Cities.Where(c => c.id == firstID);
foreach(var c in cityList)
{
var tempCity = c;
cities = cities.Union(dc.zs_Cities.Where(cty => cty.id == tempCity.id));
}