How to assign List values to list ValueTuples? - c#

I have method which return List and I wanna try to populate ValueTuple from another standard list I get error:
Cannot implicitly convert type 'System.Collections.Generic.List<(long PaymentId, long TransactionId)>' to 'System.Collections.Generic.List>'
The code looks like below:
public async Task<List<ValueTuple<(long, long)>>> CreditTransactionAsync(CancellationToken cancellationToken)
{
List<(long PaymentId, long TransactionId)> paymentTransactionList = new List<ValueTuple<long, long>>();
var paymentTransactions = _dbContext.PaymentTransactions
.AsEnumerable()
.Where(x => transactionIdsList.Any(a => a.TransactionId == x.TransactionId))
.Select(x => new
{
PaymentId = x.PaymentId,
TransactionId = x.TransactionId
})
.ToList();
// This line shows error..
paymentTransactionList = paymentTransactions.Select(x => (PaymentId: x.PaymentId, TransactionId: x.TransactionId));
return paymentTransactionList;
}

You can rewrite your Select statement a little bit and map transactions to list of tuple (long, long) directly, without intermediate anonymous type
.Select(x => (x.PaymentId, x.TransactionId))
The full code
var paymentTransactions = _dbContext.PaymentTransactions
.AsEnumerable()
.Where(x => transactionIdsList.Any(a => a.TransactionId == x.TransactionId))
.Select(x => (x.PaymentId, x.TransactionId))
.ToList();
return paymentTransactions;
You also should properly declare the return type of your method, like Task<List<(long paymentId, long transactionId)>>

ValueTuple<(long, long)> is a value tuple that contains a single element that is a value tuple that contains two elements.
You probably meant List<(long, long)>, not List<ValueTuple<(long, long)>>, however personally I'd say "don't use ValueTuple<...> on public APIs", but if you do: at least name them.
Also: AsEnumerable() on a db-context is usually a terrible mistake - especially before a Where:
public async Task<List<(long PaymentId, long TransactionId)>> CreditTransactionAsync(CancellationToken cancellationToken = default)
{
var localList = transactionIdsList.Select(a => a.TransactionId).ToList();
return _dbContext.PaymentTransactions
.Where(x => localList.Contains(x.TransactionId))
.Select(x => new { x.PaymentId, x.TransactionId })
.AsEnumerable()
.Select(x => (x.PaymentId, x.TransactionId))
.ToList();
}

Related

ToList() method call throws a syntax error at runtime when ordering a list in a union

return await result.Select(student => new MarkSheetsStudentByIdDto
{
Id = student.RegId,
FullName = student.FullName,
AnnualMarkSheets = student.TermOne
.Select(x => new MarkSheetDto
{
Rank = x.Rank
...
Comments = student.Comments.Where(x => x.StudentId.Equals(student.RegId)).Select(x => x.CommentText)
}).Union(student.TermTwo
.Select(x => new MarkSheetDto
{
Rank = x.Rank
...
Comments = student.Comments.Where(x => x.StudentId.Equals(student.RegId)).Select(x => x.CommentText)
})).OrderBy(c => c.Rank).ToList()
}).ToList();
For the above example code snippet, I am getting the following error at runtime.
42601: syntax error at or near \"SELECT\"\r\n\r\nPOSITION: 5680
I used ToList() method otherwise I am getting the following error.
Collections in the final projection must be an 'IEnumerable' type
such as 'List'. Consider using 'ToList' or some other mechanism to
convert the 'IQueryable' or 'IOrderedEnumerable' into an
'IEnumerable'.
Can anyone please guide me on how to address this scenario?
Try to load data via Include and then do projection on the client-side:
var rawResult = await result
.Include(x => x.Comments)
.Include(x => x.TermOne)
.Include(x => x.TermTwo)
.Take(1)
.ToListAsync(cancellationToken);
return rawResult.Select(student => new MarkSheetsStudentByIdDto
{
Id = student.RegId,
FullName = student.FullName,
AnnualMarkSheets = student.TermOne
.Select(x => new MarkSheetDto
{
Rank = x.Rank
...
Comments = student.Comments.Where(x => x.StudentId.Equals(student.RegId)).Select(x => x.CommentText)
}).Union(student.TermTwo
.Select(x => new MarkSheetDto
{
Rank = x.Rank
...
Comments = student.Comments.Where(x => x.StudentId.Equals(student.RegId)).Select(x => x.CommentText)
})).OrderBy(c => c.Rank).ToList()
}).ToList();
Includes can be replaced with Select later when you experienced that not needed data is requested from database.

how to iterate with Linq to create list of objets?

I'm trying to iterate from a list with Linq in order to create and return a model, with contains a list of item and a quantity of total items.
The object that must be returned is as following:
public class ListeArticlesModel
{
public List<TuileArticleModel> Items { get; set; }
public int Quantity { get; set; }
}
I'm currently stuck with this :
result.Actualites = tousLesArticlesFromDb
.ToList()
.Where(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites)
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage)
.Select(
a => new ListeArticlesModel
{
Items = new List<TuileArticleModel>
{
// returns a TuileArticleModel
getItem(a)
},
})
.FirstOrDefault();
This is not what I want. If I remove the .FirstOrDefault() I get an IEnumerable
I know I am missing something. I'm building a new ListeArticleModel for each "a" item and then I just take the first one built but I don't see how to get out of here...
(ps : I graduated a few weeks ago. I am new to C#. I know it may be basics. I am trying to learn them :D)
I tried this:
var actus = tousLesArticlesFromDb
.ToList()
.Where(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites)
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage);
This gives me an IEnumerable (using umbraco here) which contains the criteriaModel.NbrItemsParPage IPublishedContent items of type "Actualites" that I want...
Now, for each I'd like to create a new TuileArticleModel to feed the Items proptery (a List<TuileArticleModel>) of my result.Actualites...
EDIT :
I think I just resolved my problem by exposing it to you guys, and reading the comments. So I did this :
var actus = tousLesArticlesFromDb
.ToList()
.Where(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites)
.Take(criteriaModel.NbrItemsParPage);
result.Actualites.Items = actus.Select(a => {return getItem(a); }).ToList();
or in one statement :
result.Actualites = new ListeArticlesModel
{
Items = tousLesArticlesFromDb
.Where
(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites
)
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage)
.Select(a => { return getItem(a); }).ToList(),
};
Get the resultset first:
var results = tousLesArticlesFromDb
.Where
(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites
)
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage);
Then pass it into the new object:
result.Actualites = new ListeArticlesModel
{
Items = results.ToList()
};
Or, if you want to do it all in one statement:
result.Actualites = new ListeArticlesModel
{
Items = tousLesArticlesFromDb
.Where
(
a => a.GetPropertyValue<IEnumerable<IPublishedContent>>("ficheArticle_typeDeContenu")
.FirstOrDefault()?.Name == #EnumResources.TypeDeContenu_Actualites
)
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage);
.ToList()
};
.ToList()
instead of
.FirstOrDefault()
Another thing to note is
tousLesArticlesFromDb
.Where( *** )
.OrderBy(a => a.CreateDate)
.Take(criteriaModel.NbrItemsParPage)
.Select(
a => new ListeArticlesModel
{
Items = new List<TuileArticleModel>
{
// returns a TuileArticleModel
getItem(a)
},
})
This will return a IQueryable, no processing has been done yet, you've just created the query for it.
Calling .ToList() will execute the IQueryable and return the result as a list.

Reusing Lambda Select Fragments in LINQ

I would like to be able to reuse fragments of my select lambda expressions in my Entity Framework Core 2.0 queries.
For example:
var result = await ctx.Customers
.Select(cust => new CustomerDto {
CustomerId = cust.Id,
CustomerName = cust.Name,
CurrentValue = cust.Orders
.Where(order => order.OrderDate >= DateTime.Now.AddDays(-30)
.Sum(order => order.TotalValue)
})
.ToListAsync();
Since I might want to calculate the CurrentValue property in other queries (in practice the sub-query is more complex than this), I would ideally like to refactor the above code to something like:
var result = await ctx.Customers
.Select(cust => new CustomerDto {
CustomerId = cust.Id,
CustomerName = cust.Name,
CurrentValue = CalculateCustomerCurrentValueExpr(cust)
})
.ToListAsync();
I have created Linq predicates using a Linq.Expression, but I have been unable to find a way to use an Expression as an element of the select statement.
Any help would be much appreciated.
Update - Performance with .AsExpandable()/.Invoke()
For anyone interested, I ran some test code ten times which produced the following result:
Standard Inline Code: 17ms (58,609 ticks)
With .AsExpandable() and inline code 16ms (58,029 ticks)
With .AsExpandable() and .Invoke() 16ms (58,224 ticks)
I suspect that if more test cycles had been run, the average processing time for all three scenarios would have been the same - at least with the level of accuracy I could measure at (simple StopWatch()).
Thanks to all contributors, particularly SergeyA for the solution and Ivan Stoev for the simple explanation of .AsExpandable()
You can reuse expressions with AsExpandable extension from LinqKit liblary (http://www.albahari.com/nutshell/linqkit.aspx).
Example:
Expression<Func<Customer,long>> func = c => c.Orders
.Where(order => order.OrderDate >= DateTime.Now.AddDays(-30)
.Sum(order => order.TotalValue);
var result = await ctx.Customers
.AsExpandable() // this allow to unwrap injected expression
.Select(cust => new CustomerDto {
CustomerId = cust.Id,
CustomerName = cust.Name,
CurrentValue = func.Invoke(cust) // this inject predefined expression
})
.ToListAsync();
I store my expressions in a static file and reuse the expressions where I need them to ensure to include all related data. Maybe this can work for you as well
In GetStore() I reuse an expression called ClientAccess and passes that to the expression at ShopExpressions.
GetPage() uses a simple straight forward implementation.
ShopExpressions.cs:
public static IQueryable<IStore> StoreLite(IQueryable<IStore> dbSet)
{
var result = dbSet
.Include(str => str.VATs)
.ThenInclude(vat => vat.VAT)
.ThenInclude(vat => vat.Culture)
.ThenInclude(cult => cult.Items)
.ThenInclude(itm => itm.Culture)
.Include(str => str.Options)
.ThenInclude(opt => opt.Items)
.ThenInclude(itm => itm.Option)
.Include(str => str.Cultures)
.ThenInclude(cult => cult.Items)
.ThenInclude(itm => itm.Culture)
.ThenInclude(cult => cult.Items)
.ThenInclude(itm => itm.Culture)
.Include(str => str.Pages)
.ThenInclude(page => page.Sections)
.ThenInclude(section => section.Elements);
return result;
}
public static IQueryable<IStore> Store(IQueryable<IStore> dbSet)
{
var result = StoreLite(dbSet)
.Include(str => str.Categorys)
.ThenInclude(cat => cat.Products)
.ThenInclude(prd => prd.InfoItems)
.ThenInclude(itm => itm.Culture)
.ThenInclude(cult => cult.Items)
.ThenInclude(itm => itm.Culture);
return result;
}
public static IQueryable<IPage> Page(IQueryable<IPage> dbSet)
{
var result = dbSet
.Include(page => page.Sections)
.ThenInclude(sec => sec.Elements)
.Include(page => page.CSS)
.Include(page => page.Script)
.Include(page => page.Meta);
return result;
}
Controller.cs:
[HttpGet]
public async Task<IStore> GetStore(int id)
{
IStore result = await ShopExpressions.Store(GenericExpressions.ClientAccess(this.Worker.GetRepo<Store>().DbSet))
.SingleAsync(str => str.Id.Equals(id));
this.Worker.ValidateClientAccess(result);
return result;
}
[HttpGet]
public async Task<IStore> GetStoreLite(int id)
{
IStore result = await ShopExpressions.StoreLite(GenericExpressions.ClientAccess(this.Worker.GetRepo<Store>().DbSet))
.SingleAsync(str => str.Id.Equals(id));
this.Worker.ValidateClientAccess(result);
return result;
}
[HttpGet]
public async Task<IPage> GetPage(int id)
{
IPage result = await ShopExpressions.Page(this.Worker.GetRepo<Page>().DbSet)
.SingleAsync(page => page.Id.Equals(id));
return result;
}

C# LINQ: Can we return same instance from GroupBy instead of doing new?

I have a collection where I need to group and find max from that group. So I did
var foo = foobar.GroupBy(x => x.Name)
.Select(x => new Foo { Name = x.Key, Version = x.Max(v => v.Version)))
.ToList();
If there are say more that 2 properties, is it possible to return same object instead of creating new?
Sure, use OrderByDescending on the group and then First to get the max-version-object:
var maxVersionObjectByName = foobar
.GroupBy(x => x.Name)
.Select(grp => grp.OrderByDescending(x => x.Version).First())
.ToList();

Return data type for an AnonymousType#1

What is the correct return data type for an AnonymousType#1?
This code is working.
var c = p.IQueryableGetAll()
.Where(r => r.Gender == "M")
.Select(r => new { r.FirstName, r.LastName })
.ToList();
But when I placed it inside a function, error occurs.
public List<object> GetFirstNameAndLastNameOfMales()
{
var p = new Program();
return p.IQueryableGetAll()
.Where(r => r.Gender == "M")
.Select(r => new { r.FirstName, r.LastName })
.ToList();
}
Here's the error
Cannot implicitly convert type
System.Collections.Generic.List<AnonymousType#1> to
System.Collections.Generic.List<object>
How is it done correctly? Thank you.
You query in both cases is correct. However that causes the problem in the second case is the return type of your method.
You could try this one:
return p.IQueryableGetAll()
.Where(r => r.Gender == "M")
.Select(r => new { r.FirstName, r.LastName }).ToList<Object>();
The reason why you are getting this error is because
Select(r => new { r.FirstName, r.LastName })
select an anonymous type for each element in the sequence that has Gender==M. An the .ToList() at the end of your query creates a List of objects that have as their type this anonymnous type with these two properties.
A better option would be to create a class that will hold these two properties, let's call it Customer and then do the following:
public List<Customer> GetFirstNameAndLastNameOfMales()
{
var p = new Program();
return p.IQueryableGetAll()
.Where(r => r.Gender == "M")
.Select(r => new Customer {
FirstName = r.FirstName,
LastName = r.LastName
}).ToList();
}
It doesn't work because List<T> is not covariant - it is a list of a specific type, so you can't add any object to it. You can either:
Create a concrete type and use it
Cast your result to object :
.Select(r => (object)(new { r.FirstName, r.LastName }))
change the return type to IEnumerable<object> which is covariant (since you can't add to it)
Option 1 is the cleanest but requires additional code - options 2 and 3 are easy on the query side but require reflection or dynamic on the client side (and run-time vulnerability, and lack of compile-time safety, etc.).

Categories

Resources