How to instantiate an object within a linq query - linq

This is kinda theoretical question,
I was looking at someone else' code (below) and my simple solution was to instantiate the collection outside linq, but I can guess there will be cases where I'd want to instantiate the objects inside the query, and perhaps only on a selection of elements.
Here's a simplified example of how this was being done (badly).
var pods = (from n in ids
where new Node(Convert.ToInt32(n)).HasValue("propertyName")
select new
{
Id = Convert.ToInt32(n),
Url = new Node(Convert.ToInt32(n)).Url,
Name = new Node(Convert.ToInt32(n)).Title()
}).ToList();
Irrelevant Note: in this case the Node constructor is getting data from a memory cache.
How can I improve this example to only instantiate each object once using linq?
Cheers.
Murray.

Use a let clause like this:
var pods = (
from n in ids
let id = Convert.ToInt32(n)
let node = new Node(id)
where node.HasValue("propertyName")
select new
{
Id = id,
Url = node.Url,
Name = node.Title()
}
).ToList();
For more information please see let clause (C# Reference):
In a query expression, it is sometimes
useful to store the result of a
sub-expression in order to use it in
subsequent clauses. You can do this
with the let keyword, which creates a
new range variable and initializes it
with the result of the expression you
supply. Once initialized with a value,
the range variable cannot be used to
store another value. However, if the
range variable holds a queryable type,
it can be queried.

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

Select one unique instance from LINQ query

I'm using LINQ to SQL to obtain data from a set of database tables. The database design is such that given a unique ID from one table (Table A) one and only one instance should be returned from an associated table (Table B).
Is there a more concise way to compose this query and ensure that only one item was returned without using the .Count() extension method like below:
var set = from itemFromA in this.dataContext.TableA
where itemFromA.ID == inputID
select itemFromA.ItemFromB;
if (set.Count() != 1)
{
// Exception!
}
// Have to get individual instance using FirstOrDefault or Take(1)
FirstOrDefault helps somewhat but I want to ensure that the returned set contains only one instance and not more.
It sounds like you want Single:
var set = from itemFromA in this.dataContext.TableA
where itemFromA.ID == inputID
select itemFromA.ItemFromB;
var onlyValue = set.Single();
Documentation states:
Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.
Of course that means you don't get to customize the message of the exception... if you need to do that, I'd use something like:
// Make sure that even if something is hideously wrong, we only transfer data
// for two elements...
var list = set.Take(2).ToList();
if (list.Count != 1)
{
// Throw an exception
}
var item = list[0];
The benefit of this over your current code is that it will avoid evaluating the query more than once.

NHibernate IQueryable doesn't seem to delay execution

I'm using NHibernate 3.2 and I have a repository method that looks like:
public IEnumerable<MyModel> GetActiveMyModel()
{
return from m in Session.Query<MyModel>()
where m.Active == true
select m;
}
Which works as expected. However, sometimes when I use this method I want to filter it further:
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.ID < 100
select new { m.Name };
Which produces the same SQL as the first one and the second filter and select must be done after the fact. I thought the whole point in LINQ is that it formed an expression tree that was unravelled when it's needed and therefore the correct SQL for the job could be created, saving my database requests.
If not, it means all of my repository methods have to return exactly what is needed and I can't make use of LINQ further down the chain without taking a penalty.
Have I got this wrong?
Updated
In response to the comment below: I omitted the line where I iterate over the results, which causes the initial SQL to be run (WHERE Active = 1) and the second filter (ID < 100) is obviously done in .NET.
Also, If I replace the second chunk of code with
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.Items.Count > 0
select new { m.Name };
It generates the initial SQL to retrieve the active records and then runs a separate SQL statement for each record to find out how many Items it has, rather than writing something like I'd expect:
SELECT Name
FROM MyModel m
WHERE Active = 1
AND (SELECT COUNT(*) FROM Items WHERE MyModelID = m.ID) > 0
You are returning IEnumerable<MyModel> from the method, which will cause in-memory evaluation from that point on, even if the underlying sequence is IQueryable<MyModel>.
If you want to allow code after GetActiveMyModel to add to the SQL query, return IQueryable<MyModel> instead.
You're running IEnumerable's extension method "Where" instead of IQueryable's. It will still evaluate lazily and give the same output, however it evaluates the IQueryable on entry and you're filtering the collection in memory instead of against the database.
When you later add an extra condition on another table (the count), it has to lazily fetch each and every one of the Items collections from the database since it has already evaluated the IQueryable before it knew about the condition.
(Yes, I would also like to be the extensive extension methods on IEnumerable to instead be virtual members, but, alas, they're not)

linq problem with distinct function

I am trying to bind distinct records to a dropdownlist. After I added distinct function of the linq query, it said "DataBinding: 'System.String' does not contain a property with the name 'Source'. " I can guarantee that that column name is 'Source'. Is that name lost when doing distinct search?
My backend code:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
var query = (from p in dc.Promotions
select p.Source).Distinct();
return query;
}
Frontend code:
PromotionDAL dal = new PromotionDAL();
ddl_Source.DataSource = dal.GetAllSource();
ddl_Source.DataTextField = "Source";
ddl_Source.DataValueField = "Source";
ddl_Source.DataBind();
Any one has a solution? Thank you in advance.
You're already selecting Source in the LINQ query, which is how the result is an IQueryable<string>. You're then also specifying Source as the property to find in each string in the databinding. Just take out the statements changing the DataTextField and DataValueField properties in databinding.
Alterantively you could remove the projection to p.Source from your query and return an IQueryable<Promotion> - but then you would get distinct promotions rather than distinct sources.
One other quick note - using query syntax isn't really helping you in your GetAllSources query. I'd just write this as:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
return dc.Promotions
.Select(p => p.Source)
.Distinct();
}
Query expressions are great for complicated queries, but when you've just got a single select or a where clause and a trivial projection, using the dot notation is simpler IMO.
You're trying to bind strings, not Promotion objects... and strings do not have Source property/field
Your method returns a set of strings, not a set of objects with properties.
If you really want to bind to a property name, you need a set of objects with properties (eg, by writing select new { Source = Source })

Entity Framework - LinQ projection problem

I want to create an Entity Object from a LinQ statement, but I don't want to load all its columns.
My ORDERS object has a lot of columns, but I just want to retrieve the REFERENCE and OPERATION columns so the SQL statement and result will be smaller.
This LinQ statement works properly and loads all my object attributes:
var orders = (from order in context.ORDERS
select order);
However the following statement fails to load only two properties of my object
var orders = (from order in context.ORDERS
select new ORDERS
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION
});
The error thrown is:
The entity or complex type
'ModelContextName.ORDERS' cannot be
constructed in a LINQ to Entities
query.
What is the problem? Isn't it possible to partially load an object this way?
Thank you in advance for your answers.
ANSWER
Ok I should thank you both Yakimych and Dean because I use both of your answers, and now I have:
var orders = (from order in context.ORDERS
select new
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION,
})
.AsEnumerable()
.Select(o =>
(ORDERS)new ORDERS
{
REFERENCE = o.REFERENCE,
OPERATION = o.OPERATION
}
).ToList().AsQueryable();
And I get exactly what I want, the SQL Statement is not perfect but it returns only the 2 columns I need (and another column which contains for every row "1" but I don't know why for the moment) –
I also tried to construct sub objects with this method and it works well.
No, you can't project onto a mapped object. You can use an anonymous type instead:
var orders = (from order in context.ORDERS
select new
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION
});
The problem with the above solution is that from the moment you call AsEnumerable(), the query will get executed on the database. In most of the cases, it will be fine. But if you work with some large database, fetching the whole table(or view) is probably not what you want. So, if we remove the AsEnumerable, we are back to square 1 with the following error:
The entity or complex type 'ModelContextName.ORDERS' cannot be constructed in a LINQ to Entities query.
I have been struggling with this problem for a whole day and here is what I found. I created an empty class inheriting from my entity class and performed the projection using this class.
public sealed class ProjectedORDERS : ORDERS {}
The projected query (using covariance feature):
IQueryable<ORDERS> orders = (from order in context.ORDERS
select new ProjectedORDERS
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION,
});
Voilà! You now have a projected query that will map to an entity and that will get executed only when you want to.
I think the issue is creating new entities within the query itself, so how about trying this:
context.ORDERS.ToList().Select(o => new ORDERS
{
REFERENCE = o.REFERENCE,
OPERATION = o.OPERATION
});

Resources