This question already has an answer here:
How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1
(1 answer)
Closed last month.
After upgrading from .Net2.2 to .Net7, the following LINQ expression fails with this error "LINQ expression could not not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly":
string excludeString = "XX";
string excludeString2 = "XX";
var groupfilter = await _db.UserGroup.Where(q => q.UserName == currUserName && q.IsActive == false && q.WorkGroupName == "BB").ToListAsync();
if (groupfilter.Any())
excludeString = "BB";
groupfilter = await _db.UserGroup.Where(q => q.UserName == currUserName && q.IsActive == false && q.WorkGroupName == "TS").ToListAsync();
if (groupfilter.Any())
excludeString2 = "TS";
DriveListViewModel model = new DriveListViewModel()
{
Drive = await _db.Drive
.Where(m => m.StatusId == 5 || m.StatusId == 1010 || m.StatusId == 1012)
.Where(m => m.LoadingComplete == null)
.Where(m => !m.UnitCode.Contains(excludeString))
.Where(m => !m.UnitCode.Contains(excludeString2))
.Include(s => s.DriveStatus)
.Include(d => d.Location)
.Include(f => f.Item)
.GroupBy(m => m.RegistrationNumber)
.Select(m => m.FirstOrDefault())
.OrderBy(m => m.DriverToLoad)
.ToListAsync(),
machineryList = await _db.Machinery.ToListAsync(),
cleaningList = await _db.Cleaning.ToListAsync(),
};
Disabling these 3 lines removes the error:
.GroupBy(m => m.RegistrationNumber)
.Select(m => m.FirstOrDefault())
.OrderBy(m => m.DriverToLoad)
...however, I need this list to be distinct on "RegistrationNumber", so I need a modified query to obtain the same result.
Any ideas how to solve that in .Net7 ?
The EF Core 2.2 query wasn't working from the start. Instead of warning you though, EF Core silently loaded all matching rows in memory and partitioned them on the client. EF Core 2 was so limited that many important operations like GroupBy couldn't be translated to SQL, so silent client-side evaluation was used. I suspect the application is already logging EF warnings about this.
EF Core 3.0 added support for all important operations so client-side evaluation was disabled by default. Loading 10000 rows when you only wanted the SUM by 10 categories wasn't a very nice experience. Since EF Core 3, expressions that can't be translated to SQL throw an exception.
The original query doesn't do what it seems to do either, and produces somewhat random results. There's no implicit order in a table or query results unless an ORDER BY clause is used. There's no such clause in .Select(m => m.FirstOrDefault()) though, so the first object returned from the database is used, whatever that is.
If the intention is to return the first Driver by DriverToLoad for each RegistrationNumber, the following query should work:
var statuses=new[]{5,1010,1012};
var drives=await _db.Drive
...
.GroupBy(m => m.RegistrationNumber)
.Select(g => g.OrderBy(m=>m.DriverToLoad).Take(1))
.ToListAsync()
The Where clauses can be simplified quite a bit too, and contain some performance problems.
This condition translates to LIKE '%%XX%% which can't use any indexes and can result in a full table scan :
.Where(m => !m.UnitCode.Contains(excludeString))
A prefix search on the other hand can use indexes as it's essentially a range search between the prefix and the next largest string. If possible, use
.Where(m => !m.UnitCode.StartsWith(excludeString))
Related
I have two different DbSets. Now i want to query both DbSets like this:
Cars.Where(a => a.Attributes.Color.Name = "Red");
Horses.Where(a => a.Saddle.Attributes.Color.Name = "Black");
Is it possible to outsourcing the Attributes.Color.Name part in a Method without getting a "Not supported exception"? Like this:
Cars.Where(a => Reuse.CheckColor(a, "Red"));
Horses.Where(a => Reuse.CheckColor(a.Saddle, "Red"));
The where predicate is just a lambda expression (in the form of an expression tree), which you should be able to introduce as a local variable like so:
Expression<Func<MyAttributesClass, bool>> predicate = attribute => attribute.Color.Name == "Red";
Is there something in Active Record that ensures that your query does NOT return more than one record?
This is what the basic functionality would be (apologies--this isn't real code but just enough to give the idea of what I'm looking for):
Foo.where(:thing => 'this_should_be_uniq').single
def single(records)
if records.length > 1
raise # or maybe return nil or something like that
else
return records.first
end
end
Essentially, this would be a safeguard against accidentally assuming (incorrectly) that your query will always return a single record.
Thanks!
If i'm understanding your question correctly, you can use limit
Foo.where(:thing => 'this_should_be_uniq').limit(1)
you can do Foo.where(:thing => 'this_should_be_uniq').single or Foo.where(:thing => 'this_should_be_uniq').single or .limit(1)
Foo.where(:thing => 'this_should_be_uniq').first
Foo.where(:thing => 'this_should_be_uniq').last
Foo.where(:thing => 'this_should_be_uniq').limit(1)
I cannot find such method in ActiveRecord::FinderMethods.
As alternative solution, you can write it shorter using tap method in the case of raising the exception if more than two records exists:
Foo.where(:thing => 'this_should_be_uniq').tap { |r| raise "ERROR" if r.count > 1 }.first
Considering isolation from other operations, the following code is proper:
Foo.where(:thing => 'this_should_be_uniq').to_a.tap { |r| raise "ERROR" if r.size > 1 }[0]
You can also use ActiveRecord find
Foo.find_by_thing('this_should_be_uniq')
Foo.find(:first, :conditions => {:thing => 'this_should_be_uniq'})
You can also find with multiple attributes
Foo.find_by_attr1_and_attr2(attr1_value, attr2_value)
My database will not update my active_quests. All I am trying to do is replace one array of hashes with another, updated array of hashes. I assumed would be the simplest way of handling this. Here's the code:
# construct the query
query = Player.where( :_id => player_id).fields( :xp, :lvl_prgrssns, :active_quests, :completed_quests )
# get the player
player = query.first
if !player.nil?
return_val = player.set( :active_quests => [{"quest_id" => "123"}, {"quest_id" => "456"}])
logger.debug "return_val = "+return_val.to_s # comes out as 180
end
My understanding is that, if the return from a set is positive, that means that the set was successful. It returns as 180 in this simplified case but the active_quests never get updated on the player. I can go into the mongo console and execute this:
db.players.update({_id:ObjectId("50756b1896f4f5121a00000a")}, {$set:{active_quests:[{"quest_id":"1"}, {"quest_id":"2"}] }});
and active_quests will update as expected but no matter what I try in rails the update appears to go through but nothing updates.
Here are some of the many alternatives I have tried (all have been tried with and without .to_mongo and with and without player.save after them):
Player.where( :_id => params[:player_id] ).update(:active_quests => active_quests_list.to_mongo)
player.update_attributes(:active_quests => active_quests_list.to_mongo)
player_update = player.as_json
player_update["active_quests"] = active_quests_list
player.update_attributes(player_update)
return_val = query.update( "$set" => {:active_quests => player.active_quests.to_mongo} )
return_val = query.update( {:_id => params[:player_id]}, {"$set" => {:active_quests => active_quests_list.to_mongo}})
I'm hoping someone here might know what I am doing wrong.
After further investigation, it turns out that this was a problem relating to how the player variable was being updated outside of the function.
The following lines will update the record in this case (both in-memory and in the database)
player[:active_quests] << #active_quests_list
player.push_all(:active_quests => player.active_quests)
However, the player variable was local to this function in this case, and was being updated again after the function returned.
This was only discovered after careful examination of the output of "mongod -vvvvv".
I have a hash with booleans
({"u25" => true, "f26t49" => true, "o50" => true, ..});
all in all there are 19 booleans and I want to check these with a table in my db and get those data sets, how have at least one match. I had try it with
"Model.all(:conditions => hash)" or "Model.where(hash)"
but there I get the query
"..u25 == true AND f26t49 == true AND o50 == true..."
but I need something like this:
"..u25 == true OR f26t49 == true..."
I hope you could help me!
ADDITION:
I tryed to make a workaround and generate a query string out of the hash. If i write this string directly (m = Model.where("u25 == 't'")) it works but if I pass a varaible
#query = '"u25 == ' + "'t'" + '"'
m = Model.where(#query)
than m is nil!
I think that your workaround put you in the right direction. Here's what I would do:
# model.rb
def self.search_with_conditions(hash)
query = hash.map{|k,v| "#{k} == #{v}"}.join(' OR ')
where(query)
end
And then in your controller, simply call:
m = Model.search_with_conditions(hash)
Sidenote: This might work for some DBs and not for others as some use 1 as the value for true. Also, I didn't have time to try this so I may be missing some quotation marks in that condition. I think it could be '#{v}'.
EDIT: After learning a little bit more about Rails, I must add a disclaimer here:
If the strings k or v are part of some user input, this approach is susceptible to SQL injection. Use this method with care.
I have a structure that has both :id and a :group_id fields. I wish to retrieve all the data which :id equals :group_id.
I am currently working with mongoid, but I think that the operation is done the same way as it would be done when using Rails3's ActiveRecord.
Anyways. I was trying to use Message.where(:group_id => :id), which should return, for instance, this: => #<Message _id: 4e38aa132748440a7e00007d, group_id: BSON::ObjectId('4e38aa132748440a7e00007d')>. But it does not work at all (it seems as if it really should not work, but how it should, then?)
How can I query mongoid (or active record) to get only the Messages where :id equals :group_id?
EDIT:
just to show that those values are really exactly the same:
ruby-1.9.2-p180 :088 > Message.first.id
=> BSON::ObjectId('4e38aa132748440a7e00007d')
ruby-1.9.2-p180 :089 > Message.first.group_id
=> BSON::ObjectId('4e38aa132748440a7e00007d')
EDIT2:
These do not work (they have at least one message (the example above) that match)):
ruby-1.9.2-p180 :091 > Message.where("group_id = id").count
=> 0
ruby-1.9.2-p180 :092 > Message.where("group_id == id").count
=> 0
EDIT3:
Here is an example of message that should be returned: http://cl.ly/3q1r20421S3P1921101D
EDIT4:
Another piece of weirdness:
ruby-1.9.2-p180 :034 > Message.where("this._id = this.group_id").each {
|m| puts "#{m.id} = #{m.group_id}" }
4e38aa132748440a7e00007d = 4e38aa132748440a7e00007d
4e38aa132748440a7e00007e = 4e38aa132748440a7e00007d
4e38aa562748440a7e000084 = 4e38aa562748440a7e000084
4e38aa562748440a7e000085 = 4e38aa562748440a7e000084
4e38ac312748440a7e000095 = 4e38ac312748440a7e000095
4e38ac312748440a7e000096 = 4e38ac312748440a7e000095
4e38ac312748440a7e000097 = 4e38ac312748440a7e000095
Why it returns those values, even though in some cases they are not equal? Aaaarghhhhh
EDIT5: Another piece of weirdness and a small inconclusive conclusion:
ruby-1.9.2-p180 :090 > Message.where("this.read == this.sender_deleted").each {
|m| puts "#{m.read.to_s},#{m.sender_deleted.to_s}" }
true,true
false,false
false,false
false,false
false,false
My query works with boolean values! Hence, it SEEMS it is not working only with BSON::ObjectId() object comparisons (I also could not retrieve messages by doing Message.where(:id => Message.first.id [edit: I managed to do this by using :_id instead of :id, but this did not help me on the remaining of my problems]). How can I compare these damn identifiers on the same object? I really need to, and I am afraid there is no elegant alternative (storing those exact same IDs as strings would be just too weird, please do not suggest it :/ ). In the end, this seems like a specific issue to Mongoid.
obs.: I tried all combinations that came to mind, regarding id vs. _id and = vs. ==. Is there any Hash-like query for accomplishing what I need? using something like .where(:_id => :group_id) (but not that, I mean the :group_id value, not a :group_id symbol).
Try this:
Message.where("this.group_id == this.id")
Mongoid usees MongoDB in the end, so anything which is not possible in mongodb can not be done using mongoid. The particular use case you are talking about, is not supported by mongodb as of now.
You cannot query a field against a field, you have to provide literal values to query against. I saw an issue on Mongodb Issue Tracker for supporting this, but couldn't locate it now.
The boolean value query you are mentioning, might just be a coincidence. That should also not work as expected.
Update
One can compare fields in mongodb using $where clause.
You have been missing the '$where' in Mongoid's where method
This worked for me:
Message.where('$where' => 'this.group_id == this.id')
Message.where("group_id = id")