I'm trying to run a LINQ query against an Azure DocumentDB collection. When I run my query I keep getting an AggregateException that contains an InvalidOperationException with the message:
Nullable object must have a value
I've reduced this issue to the following (somewhat contrived) example:
When I run this code, I get the above mentioned exception thrown from the call to ToArray()
public class MyDocument { ... }
public void RunQuery()
{
var query = documentDbClient
.CreateDocumentQuery<MyDocument>()
.Where(doc => GetDoc(doc) != null);
var results = query.ToArray()
}
public MyDocument GetDoc(MyDocument myDocument)
{
return myDocument;
}
In contrast, when I run the code below no exception is thrown and I get back good results from the DocumentDB collection.
public void RunQuery()
{
var query = documentDbClient
.CreateDocumentQuery<MyDocument>()
.Where(doc => doc != null);
var results = query.ToArray()
}
Why the difference in behavior between the two code samples?
Notes:
- While GetDoc() is a stand in for my more complex predicate logic, the code above reproduces the issue exactly. I'm not withholding any shenanigans inside GetDoc() or other methods :)
- The issue occurs even when GetDoc() is made static.
- Just tried to reproduce with a List<MyDocument> instead of documentDbClient and no exception was thrown. Indicates something in the underlying data provider = Azure DocumentDB's IDatabaseClient.
With help from #andrew-liu and #will (thanks!) I've been able to figure out that:
At the time of the call to Where() in my above example, the predicate inside is interpreted (rather than executed) as a LINQ expression. At the time of the call to ToArray() the LINQ expression is executed against the DocumentDB .NET SDK's LINQ provider.
Because the SDK is not aware of my GetDoc() method, it throws the exception with the cryptic message "Nullable object must have a value".
In v1.9 of the DocumentDB SDK for .Net the error message is much clearer. A DocumentQueryException is thrown with a message like "Method "GetDoc()" is not supported".
You can get similar feedback from v1.8 of the SDK by invoking ToString() on the query like so: query.ToString().
Related
I am trying to convert a child of an Entity Framework entity instance into a simple class but am running into some difficulties. Here is my relevant code:
model.BlogSearchResults = await _context.Blog
.Include(b => b.Survey)
.Select(b => new BlogSearchResult
{
Id = b.Id,
Title = b.Title,
Summary = b.Summary,
SurveySearchResult = b.Survey == null ? null : new SurveySearchResult
{
Id = b.Survey.Id,
Title = b.Survey.Title,
Description = b.Survey.Description
}
}).ToListAsync();
This is failing on the line that sets SurveySearchResult with an ArgumentException error. I believe this is because Blog.Survey is technically nullable? If I cast b.Survey.Id to a non-nullable int I end up getting an error that states: AggregateException: One or more errors occurred. (Unable to cast object of type 'System.String' to type 'Models.Blog'.).
Since Survey has a one-to-one relationship with Blog I cannot convert it into a simple object using lambda expressions as you might with a list child. I have tried something like this:
SurveySearchResult = (b.Survey => new SurveySearchResult{ ...
with no luck and have tried using a helper function to manually convert the object over in-line. Nothing seems to work. Is there a standard way of doing this I am unaware of?
There's nothing obviously wrong with your posted code. ArgumentException is thrown generally when there's something "invalid" about an argument passed to a method or constructor. Usually, you get something like ArgumentNullException or ArgumentOutOfRangeException, which derive from ArgumentException and give more explicit detail about what's actually wrong with the argument.
However, here, there's nothing that should really cause this particular exception. The only arguments you're passing are to Include and Select, and those are perfectly fine. It's possible there's something on BlogSearchResult or SurveySearchResult that's generating this error, but you haven't posted the code for those classes, making it impossible to tell.
I have 1 LINQ which used so much. I try create the method which return this LINQ like:
public static System.Linq.Expressions.Expression<Func<MyEntity, bool>> GetFilteredEntity() {
return x => true/*Some condition*/;
}
public static Func<MyEntity, bool> GetFilteredEntity() {
return x => true/*Some condition*/;
}
And use this like
db.MyEntities.Where(GetFilteredEntity());
is successfull, But! I need use it like
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity()));
This code compiled too, but every time when i use it, i got the Error:
System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
,even:
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity())).ToList();
throw this Exception too.
But,
db.ParentEntities.Where(entity => entity.MyEntities.Where(x => true/*Some condition*/))
still works fine!
So why it happend, and have some way to get round this?
Final working code
public static Expression<Func<MyEntity, bool>> GetFilteredEntity() {
return x => true/*Some condition*/;
}
and
var expression = GetFilteredEntity();
db.ParentEntities.Where(entity => entity.MyEntities.AsQueryable().Where(expression ));
Also .AsQueryable() thanks to Passing func as parameter in Linq to Entities and 'Internal .NET Framework Data Provider error 1025' error
In your first example the function is called and translated into an expression before it is even sent to the query provider. In the next two examples the function call is embedded within an expression that is sent to the query provider, and that query provider doesn't know what to do with that function call, so it just throws an exception. When you embed the actual expression in another expression, there is no function call to confuse the query provider.
As for a solution, just pull out the function call into a variable. The query provider is smart enough to see that you used a closed over variable, and will pull out its value. For a function call it's just not sure if it should evaluate it or try to translate it into something that should be done on the DB's end. Trying to do some of both would just be very confusing and hard to work with, both for the query provider, and for people using it. To simplify matters, function calls with expressions are never executed prior to sending the query. As for a closed over variable, there is no other way that it could be treated, so there isn't any other behavior to confuse it with.
var expression = GetFilteredEntity();
db.ParentEntities.Where(entity => entity.MyEntities.Where(expression ));
Looks like LazyLoading might be the culprit, have you tried popping the ToList() on the parameters?
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity()).ToList());
I've checked other questions and I believe this should work:
private static List<Duck> GetDucks(this DataServiceContext ctx, params int[] ids)
{
return ctx.CreateQuery<Duck>("Ducks").Where(x => ids.Contains(x.DuckID)).ToList();
}
I got a NotSupportedException: The method 'Contains' is not supported.
I changed the query to
Where(x => ids.Any(id => id == x.DuckID)
I received the message: The method 'Any' is not supported when MaxProtocolVersion is less than '3.0'
I then constructed the DataServiceContext a different way:
public static DataServiceContext Context
{
get { return new DataServiceContext(baseUri, DataServiceProtocolVersion.V3); }
}
I then received the message: NotSupportedException: The source parameter for the 'Any' method has to be either a navigation or a collection property
I think I could do with some advice before going any further.
EDIT
I did use MaxProtocolVersion 3 both client and server side.
In any case, this particular error message has gone away for now as I am no longer trying to return a subset of data. I simply get the full list of entities up front to work with (though this will need further consideration/optimization). I mistakenly believed that if I had a temp DataServiceContext that was empty I would have to retrieve the entities I wanted to update and then save them back (influenced by various examples on performing updates I found). Of course the method AttachTo(EntitySetName, entity) is the correct solution. Then you can call UpdateObject(entity) prior to TempContext.SaveChanges().
From what I have learned it looks like LINQ support in this scenario is limited, though I still do not know why contains does not work on a DataServiceQuery. The MSDN documentation suggests it is supported. Queries to the DataServiceContext resolve urls in OData format:
http://services.odata.org/OData/OData.svc/Category(1)/Products?$top=2&$orderby=name
The solution I used - adding a WCF Data Service operation/method:
[WebGet]
public IQueryable<Thing> GetThingsByID(string ids)
{
return CurrentDataSource.Things.Where(x => ids.Contains(x.ThingID.ToString())).AsQueryable();
}
The above should allow me to filter:
WCF Data Service operations
Calling Service Operations
I pass in ids as CSVs as only primitive data types may be passed as parameters
Note:
Contains works on the DBContext.
The following method works properly in my service layer:
public override IList<City> GetAll()
{
var query = from item in _tEntities
select item;
query.Load();
return _tEntities.Local;
}
but when i try to run following method, the method returns old data loaded by GetAll() method.
public override IList<City> GetAll(Func<City, bool> predicate)
{
var query = from item in _tEntities
select item;
query.Where<City>(predicate);
query.Load();
return _tEntities.Local;
}
What is the problem and how can i fix them?
How can i use local method in this sample and reload new data into local(cache)?
You are looking at the wrong problem. What you are most likely seeing is a result of the fact that when you do the first query, the local cache is empty. So it only returns the results from your query. But when you do the second, it's returning the results of your first query AND your second query.
This comes down to the fact that you are using a shared DbContext between all your methods. Local contains a cache of all records the context has retrieved, not just the most recent query.
The correct solution is to not use Local in this manner. Even better, don't use a shared context since this can lead to context cache bloat.
I'm not too sure what you are trying to achieve with a .Load method here but it seems like you want the following.
public override IList<City> GetAll(Func<City, bool> predicate)
{
return _tEntities.Where<City>(predicate).ToList();
}
query.Where<City>(predicate);
This doesn't change query. The query.Load() on the next line ignores the predicate: you're calling query.Load() and not query.Where<City>(predicate).Load(). It's as if you had written
int i = 3;
i + 1;
Console.WriteLine(i); // still prints 3
In that example, C# does not really actually allow an addition to be used as a statement, but .Where(predicate) is a method call, and method calls can be used as such, even if they return values.
This is not your only issue (see the other answers), but my guess is that this issue is the one that leads to the unexpected results you're seeing.
Why does this throw System.NotSupportedException?
string foo(string f) { return f; }
string bar = "";
var item = (from f in myEntities.Beer
where f.BeerName == foo(bar)
select f).FirstOrDefault();
Edit: Here's an MSDN reference that (kind of) explains things...
Any method calls in a LINQ to Entities
query that are not explicitly mapped
to a canonical function will result in
a runtime NotSupportedException
exception being thrown. For a list of
CLR methods that are mapped to
canonical functions, see CLR Method to
Canonical Function Mapping.
See also http://mosesofegypt.net/post/LINQ-to-Entities-what-is-not-supported.aspx
EDIT: Okay, the code blows up because it doesn't know what to do with the call to foo(). The query is built up as an expression tree which is then converted to SQL.
The expression tree translator knows about various things - such as string equality, and various other methods (e.g. string.StartsWith) but it doesn't know what your foo method does - foo() is a black box as far as it's concerned. It therefore can't translate it into SQL.
The second version will fail as soon as you try to iterate over it. You can't use a locally defined method in an IQueryable<> where clause (of course, you can, but it will fail when the LINQ provider tries to translate it into SQL).
Because in the 2nd query no actual query is executed. Try adding ToList() where SingleOrDefault() is.
It's probably because the SQL-generation functionality isn't able to determine what to do with your foo() function, so can't generate output for it.