Method Syntax in LINQ: try to use variables in a long query - linq

LINQ newbie here.
I have a long LINQ query, called it MYLONGQUERY, that returns a collection of certain class instances. If the list is not empty, I want to return a property (MYPROPERTY) of the first instance; otherwise it returns some default value (DEFAULTPROPERTY). So the query looks like this
(0 != MYLONGQUERY.count()) ? MYLONGQUERY.FirstOrDefault().MYPROPERTY: DEFAULTPROPERTY
This works fine. However, I don't like the fact that I have to repeat MYLONGQUERY before and after "?". I have been trying Let and Into, but have not been able to get those to work. And it has to be Method Syntax, not Query Syntax. Suggestions? Appreciate it.

You have to select the property first, then you can specify the default-value with DefaultIfEmpty:
var prop = MYLONGQUERY
.Select(x => x.MYPROPERTY)
.DefaultIfEmpty(DEFAULTPROPERTY) // new default-value
.First(); // never exception

Related

Is it possible to use method in the group by method in Linq?

I am trying to group by my custom method. For example, if the group id is something, then I want to return 1 or 0 from the method of GetClientGroup, then I want to group by the value. But I am getting error such as this.
Error
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
await (from o in _cdsContext.Order
where o.ClienteleId == clienteleId && o.DeliveryDate >= new DateTime(2020, 06, 29).Date
&& o.DeliveryDate != null
group o by new
{
o.ClienteleId,
o.DeliveryDate,
ClientGroup= o.OrderTypeId == 22 ? 259 : GetClientGroup(clienteleId, (int)o.GroupId),
}
into g
select new { ClienteleId = g.Key.ClienteleId}).ToListAsync()
I think you get this error at run time, not at compile time. Am I right?
IEnumerable and IQueryable
You should be aware of the difference between IEnumerable<...> and IQueryable<...>.
Object that implement IEnumerable<...> or IQueryable<...> represents the potentional to give you an enumerable sequence. Once you've got the sequence, you can ask for the first element, and once you've got this, you can ask for the next element as long as there is an element.
This iterating over the elements is usually done using a foreach (var element in sequence) {...}. This translates into the following:
IEnumerable<MyType> sequence = ... // the potential to get iterator
IEnumerator<MyType> enumerator = sequence.GetEnumerator(); // get the iterator
while (enumerator.MoveNext()) // iterate
{ // as long as there are items
MyType item = enumerator.Current; // fetch the item
ProcessItem(item); // and process it.
}
The LINQ methods that don't return IEnumerable<...> or IQueryable<...>, like ToList, ToDictionary, Count, Any, FirstOrDefault, etc internally all use foreach or GetEnumerator
An object that implements IEnumerable<...> is meant to be processed by your local process. The object holds everything to be able to iterate, inclusive calls to local methods.
On the other hand, an object that implements IQueryable<...>, like your _cdsContext.Order is meant to be processed by another process, usually a database management system.
This object holds an Expression and a Provider. The Expression is a generic form of the data that you want to query. The Provider knows who has to execute the query, and what language is used (usually SQL)
Concatenating LINQ statements won't execute the query, they will only change the Expression. When (deep inside) GetEnumerator() is called, the Expression is sent to the Provider, who will translate it into SQL and execute the query at the DBMS. The fetched data is represented as an iterator to your process, who will repeatedly call MoveNext() and Current.
Back to your question
Your GroupBy contains a call to a local method. The GroupBy won't execute the query, it will only change the Expression. In the end you do a ToList. The Tolist will do a GetEnumerator(). The Expression is sent to the Provider who will try to translate it into SQL.
Alas, your provider doesn't know your local method GetClientGroup, and thus can't convert it into SQL. In fact, apart from all your local methods, there are also several LINQ methods that can't be translated into SQL. See Supported and Unsupported LINQ methods (LINQ to entities)
Your compiler doesn't know which methods the provider can translate, so the compiler won't complain. Only at run time, when you do a ToList, the problem is detected.
How to solve the problem
The problem is in parameter KeySelector of Queryable.GroupBy
Expression<Func<TSource,TKey>> keySelector
Alas you forgot to write what GetClientGroup does. It seems that it takes the ClienteleId and the GroupId of an Order, and returns an integer that is similar to a ClientGroup.
The most easy would be to replace the call to GetClientGroup with the code that is in that method. Don't call any other methods
DateTime deliveryLimitDate = new DateTime(2020, 06, 29).Date;
var result = dbContext.Orders
.Where (order => order.ClienteleId == clienteleId
&& order.DeliveryDate != null
&& order.DeliveryDate >= deliveryLimitDate)
.GroupBy(order => new // Parameter KeySelector
{
ClienteleId = order.ClienteleId,
DeliveryDate = order.DeliveryDate,
ClientGroup= order.OrderTypeId == 22 ? 259 :
// formula in GetClientGroup(...)
// for example
(int)order.GroupId << 16 + order.ClienteleId
// parameter ResultSelector
group => new { ClienteleId = group.Key.ClienteleId});
Instead of a separate Select, I used the GroupBy overload with a parameter ResultSelector. Your result is a sequence of objects with only one property ClienteleId. Consider to return only a sequence of ClienteleId:
// parameter ResultSelector
group => group.Key.ClienteleId});
Alas, since I don't know your GetClientGroup, I can't give you parameter KeySelector

EF4.1 LINQ, selecting all results

I am new to LINQ queries and to EF too, I usually work with MySQL and I can't guess how to write really simples queries.
I'd like to select all results from a table. So, I used like this:
ZXContainer db = new ZXContainer();
ViewBag.ZXproperties = db.ZXproperties.All();
But I see that I have to write something inside All(---).
Could someone guide me in how could I do that? And if someone has any good link for references too, I thank so much.
All() is an boolean evaluation performed on all of the elements in a collection (though immediately returns false when it reaches an element where the evaluation is false), for example, you want to make sure that all of said ZXproperties have a certain field set as true:
bool isTrue = db.ZXproperties.All(z => z.SomeFieldName == true);
Which will either make isTrue true or false. LINQ is typically lazy-loading, so if you're calling db.ZXproperties directly, you have access to all of the objects as is, but it isn't quite what you're looking for. You can either load all of the objects at the variable assignment with an .ToList():
ViewBag.ZXproperties = db.ZXproperties.ToList();
or you can use the below expression:
ViewBag.ZXproperties = from s in db.ZXproperties
select s;
Which is really no different than saying:
ViewBag.ZXproperties = db.ZXproperties;
The advantage of .ToList() is that if you are wanting to do multiple calls on this ViewBag.ZXproperties, it will only require the initial database call when it is assigning the variable. Alternatively, if you do any form of queryable action on the data, such as .Where(), you'll have another query performed, which is less than ideal if you already have the data to work with.
To select everything, just skip the .All(...), as ZXproperties allready is a collection.
ZXContainer db = new ZXContainer();
ViewBag.ZXproperties = db.ZXproperties;
You might want (or sometimes even need) to call .ToList() on this collection before use...
You don't use All. Just type
ViewBag.ZXproperties = db.ZXproperties;
or
ViewBag.ZXproperties = db.ZXproperties.ToList();
The All method is used to determine if all items of collection match some condition.
If you just want all of the items, you can just use it directly:
ViewBag.ZXproperties = db.ZXproperties;
If you want this evaluated immediately, you can convert it to a list:
ViewBag.ZXproperties = db.ZXproperties.ToList();
This will force it to be pulled across the wire immediately.
You can use this:
var result = db.ZXproperties.ToList();
For more information on linq see 101 linq sample.
All is some checking on all items and argument in it, called lambda expression.

LINQ Query Result - dynamically get field value from a field name variable

LINQ newbie here
I am trying to get a value of a field - using a fieldName variable.
If I do a watch on row[FieldName] I do get a value - but when I do it on the actual code it will not compile.
string fieldName = "awx_name"
List<awx_property> propertyQry =
(
from property in crm.awx_propertyawx_properties
where property.awx_propertyid == new Guid(id)
select property
).ToList();
foreach (awx_property row in propertyQry)
{
//THIS DOES NOT WORK
fieldValue = row[fieldName];
}
Thanks in advance. Alternatives would be welcome as well
You keep us guessing what you are trying to do here... You need to specify the types of the objects, so it's easy for us to understand and help. Anyway, I think you are trying to get an object based on the ID. Since you are getting by Id, my guess would be the return value is a single object.
var propertyObj =( from property in crm.awx_propertyawx_properties
where property.awx_propertyid == new Guid(id)
select property
).SingleOrDefault();
if(propertyObj != null) {
fieldValue = propertyObj.GetType().GetProperty(fieldName).GetValue(propertyObj, null);
}
Of course, you need to add validation to make sure you don't get null or any other error while accessing the property value.
Hope it helps.
What type is fieldValue? What does awx_property look like? This will only work is awx_property is a key/value collection. It its not, you could use reflection instead.
If it is a key/value collection you are probably missing a cast. (row[FieldName].ToString() or something) Also you are missing a semi-colon in the foreach block.

Entity Framework LINQ Query using Custom C# Class Method - Once yes, once no - because executing on the client or in SQL?

I have two Entity Framework 4 Linq queries I wrote that make use of a custom class method, one works and one does not:
The custom method is:
public static DateTime GetLastReadToDate(string fbaUsername, Discussion discussion)
{
return (discussion.DiscussionUserReads.Where(dur => dur.User.aspnet_User.UserName == fbaUsername).FirstOrDefault() ?? new DiscussionUserRead { ReadToDate = DateTime.Now.AddYears(-99) }).ReadToDate;
}
The linq query that works calls a from after a from, the equivalent of SelectMany():
from g in oc.Users.Where(u => u.aspnet_User.UserName == fbaUsername).First().Groups
from d in g.Discussions
select new
{
UnReadPostCount = d.Posts.Where(p => p.CreatedDate > DiscussionRepository.GetLastReadToDate(fbaUsername, p.Discussion)).Count()
};
The query that does not work is more like a regular select:
from d in oc.Discussions
where d.Group.Name == "Student"
select new
{
UnReadPostCount = d.Posts.Where(p => p.CreatedDate > DiscussionRepository.GetLastReadToDate(fbaUsername, p.Discussion)).Count(),
};
The error I get is:
LINQ to Entities does not recognize the method 'System.DateTime GetLastReadToDate(System.String, Discussion)' method, and this method cannot be translated into a store expression.
My question is, why am I able to use my custom GetLastReadToDate() method in the first query and not the second? I suppose this has something to do with what gets executed on the db server and what gets executed on the client? These queries seem to use the GetLastReadToDate() method so similarly though, I'm wondering why would work for the first and not the second, and most importantly if there's a way to factor common query syntax like what's in the GetLastReadToDate() method into a separate location to be reused in several different other LINQ queries.
Please note all these queries are sharing the same object context.
I think your better of using a Model Defined Function here.
Define a scalar function in your database which returns a DateTime, pass through whatever you need, map it on your model, then use it in your LINQ query:
from g in oc.Users.Where(u => u.aspnet_User.UserName == fbaUsername).First().Groups
from d in g.Discussions
select new
{
UnReadPostCount = d.Posts.Where(p => p.CreatedDate > myFunkyModelFunction(fbaUsername, p.Discussion)).Count()
};
and most importantly if there's a way to factor common query syntax like what's in the GetLastReadToDate() method into a separate location to be reused in several different places LINQ queries.
A stored procedure would probably be one way to store that 'common query syntax"...EF, at least 4.0, works very nicely with SP's.

LINQ syntax where string value is not null or empty

I'm trying to do a query like so...
query.Where(x => !string.IsNullOrEmpty(x.PropertyName));
but it fails...
so for now I have implemented the following, which works...
query.Where(x => (x.PropertyName ?? string.Empty) != string.Empty);
is there a better (more native?) way that LINQ handles this?
EDIT
apologize! didn't include the provider... This is using LINQ to SQL
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=367077
Problem Statement
It's possible to write LINQ to SQL that gets all rows that have either null or an empty string in a given field, but it's not possible to use string.IsNullOrEmpty to do it, even though many other string methods map to LINQ to SQL.
Proposed Solution
Allow string.IsNullOrEmpty in a LINQ to SQL where clause so that these two queries have the same result:
var fieldNullOrEmpty =
from item in db.SomeTable
where item.SomeField == null || item.SomeField.Equals(string.Empty)
select item;
var fieldNullOrEmpty2 =
from item in db.SomeTable
where string.IsNullOrEmpty(item.SomeField)
select item;
Other Reading:
1. DevArt
2. Dervalp.com
3. StackOverflow Post
This won't fail on Linq2Objects, but it will fail for Linq2SQL, so I am assuming that you are talking about the SQL provider or something similar.
The reason has to do with the way that the SQL provider handles your lambda expression. It doesn't take it as a function Func<P,T>, but an expression Expression<Func<P,T>>. It takes that expression tree and translates it so an actual SQL statement, which it sends off to the server.
The translator knows how to handle basic operators, but it doesn't know how to handle methods on objects. It doesn't know that IsNullOrEmpty(x) translates to return x == null || x == string.empty. That has to be done explicitly for the translation to SQL to take place.
This will work fine with Linq to Objects. However, some LINQ providers have difficulty running CLR methods as part of the query. This is expecially true of some database providers.
The problem is that the DB providers try to move and compile the LINQ query as a database query, to prevent pulling all of the objects across the wire. This is a good thing, but does occasionally restrict the flexibility in your predicates.
Unfortunately, without checking the provider documentation, it's difficult to always know exactly what will or will not be supported directly in the provider. It looks like your provider allows comparisons, but not the string check. I'd guess that, in your case, this is probably about as good of an approach as you can get. (It's really not that different from the IsNullOrEmpty check, other than creating the "string.Empty" instance for comparison, but that's minor.)
... 12 years ago :) But still, some one may found it helpful:
Often it is good to check white spaces too
query.Where(x => !string.IsNullOrWhiteSpace(x.PropertyName));
it will converted to sql as:
WHERE [x].[PropertyName] IS NOT NULL AND ((LTRIM(RTRIM([x].[PropertyName])) <> N'') OR [x].[PropertyName] IS NULL)
or other way:
query.Where(x => string.Compare(x.PropertyName," ") > 0);
will be converted to sql as:
WHERE [x].[PropertyName] > N' '
If you want to go change the type of the collection from nullable type IEnumerable<T?> to non-null type IEnumerable<T> you can use .OfType<T>().
.OfType<T>() will remove null values and return a list of the type T.
Example: If you have a list of nullable strings: List<string?> you can change the type of the list to string by using OfType<string() as in the below example:
List<string?> nullableStrings = new List<string?> { "test1", null, "test2" };
List<string> strings = nullableStrings.OfType<string>().ToList();
// strings now only contains { "test1", "test2" }
This will result in a list of strings only containing test1 and test2.

Resources