How to add multiple arguments to lambda in C# - c#

I am currently working on a library management project. It has 3 tables, TblUser,TblBook and TBlBookStatus. When a user reserves a book, the userID from TblUser,bookID from TblBook is stored in another table(TblBookStatus). I am joining and creating a new list on my database tables on c3. They are stored as lists in my C# code. The code below creates a list that it displays the reserved books.
Note: I did not connect bookID and userID as foreign key on TblBookStatus due to some project specific reasons
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LibMan.Models.DB;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace LibMan.Pages
{
public class LibraryModel : PageModel
{
private readonly LibMan.Models.DB.LibManContext _context;
public string Message { get; set; }
public LibraryModel(LibMan.Models.DB.LibManContext context)
{
_context = context;
DisplayBook = new List<TblBook>();
; }
public IList<TblBookStatus> TblBookStatus { get; set; }
public IList<TblBook> TblBook { get; set; }
public IEnumerable<TblBook> DisplayBook { get; set; }
public IList<TblUser> TblUser{ get; set; }
public async Task OnGetAsync()
{
TblBookStatus = await _context.TblBookStatus.ToListAsync();
TblBook = await _context.TblBook.ToListAsync();
TblUser = await _context.TblUser.ToListAsync();
if (TblBookStatus != null && TblBook != null){
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId));
}
}
}
}
On the line DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId)); I display the reserved book via bookid, but not by user.
I want to display the reserved books of currently logged in user, not all users reserved books. So is there any possible way that I can implement this on the lambda statement above? I am imagining something like this, but my syntax is wrong.
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId),a=> TblUser.Any(as=> as.UserId == a.UserId);
Thanks for the help!

{ EDIT - Answer was marked as correct since behavior was provided but didn't technically answer the question }
To answer the question: "How to add multiple arguments to lambda in C#", it is important to understand that in this case, the lambda expression is being converted to the delegate type, defined as a parameter to the linq methods such as Where() and Any(). The lamda must therefore match the "signature" of the delegate type which defines its parameters and return value. In most cases, linq methods expect a delegate called a Predicate, defined as delegate bool Predicate<in T>(T obj). The converted lambda must therefore, receive one parameter of type T, being the Enumerable element type, and return a single boolean.
In summary, you cannot add arguments to lambda expression that are to be converted to delegates, it is the delegate type that determines the parameters and return type. A "multiple argument" lambda may look like this, conforming to the signature of the delegate divide, two int in, one double out:
Func<int, int, double> divide = (x, y) => { return x / y; };
To provide the behavior you requested, an alternative approach you may like to consider:
(I don't have your models to test, so consider this pseudo, but looking at your query, you could try something like...)
var DisplayBook = (from book in TblBook
from bookStatus in TblBookStatus
from user in TblUser
where (book.BookId == bookStatus.BookId) && (user.UserId == currentReader) && (user.UserId == bookStatus.ReservedBy)
select book).FirstOrDefault();

Why not simply join the datasets by UserId?
DisplayBook = (from book in TblBook
join stat in TblBookStatus
on book.BookId equals stat.BookId
join usr in TblUser
on stat.UserId equals usr.UserId
where usr.UserId = <currentUserId>
Select book).ToList();

Try this :
//assumuning current userid is in below variable.
int CurrentUserId;// Fetch it from Session object if you store userid on login
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId)
&& TblUser.Any(tb=>tb.UserId==CurrentUserId && tb.BookId==t.BookId));

Related

How to use expression to assign property in LINQ Select new

I have the following situation in a generic class:
IQueryable<TQuote> query = GetQuotes(ctx)
where ctx is a database context and GetQuotes returns DbSet<TQuote>.
Then somewhere down the code the query is executed. Its simplified form is as follows:
var list = await query
.Select(v => new
{
v.Id,
TimeZone = v.Property != null ? (int?)v.Property.TimeZone : null
// and about 10 more simple properties like v.Id above
}
.ToListAsync();
where Property is a navigation property (to another table) and TimeZone is just a regular database column / property of type int? in C#.
Everything worked until I tried to use that generic class with the entity, which does not have a navigation property Property. So, I would like to replace the "hard coded" expression v.Property != null ? (int?)v.Property.TimeZone : null by an abstract member of the class and then override it differently for different TQuote types.
I tried something along this signature:
protected abstract Expression<Func<TQuote, int?>> GetTimeZone();
but then if I use it in LINQ (either directly or assigning to some variable first), then signature of TimeZone also changes to Expression<...> and if I try to ...Compile().Invoke(v), then LINQ complains that LINQ to entities does not support that.
I saw this answer: Create a Dynamic Linq to EF Expression to Select IQueryable into new class and assign properties However, it builds the whole selector by hands and given that I have total of 16 properties that would be a pain on the neck to create and maintain. So, I wonder if I can do something about this TimeZone only but leave the rest in a simple LINQ form as above.
Is it possible and if yes, then how exactly?
Try nuget LINQKit, https://github.com/scottksmith95/LINQKit.
Below is my attempt.
LinqKit provides the AsExpandable() and Invoke(v) methods.
I made up properties like Area.TimeZone and Region.RegionalTimeZone.
using LinqKit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class QuoteResult
{
public int Id { get; set; }
public int? TimeZone { get; set; }
public string Note { get; set; }
}
abstract class QuoteHelper<TQuote> where TQuote : Quote
{
protected abstract Expression<Func<TQuote, int?>> GetTimeZone { get; }
public IEnumerable<QuoteResult> GetQuoteResults(
EfExpressionPropertyDbContext ctx)
{
IQueryable<TQuote> query = GetQuotes(ctx);
var getTimeZone = GetTimeZone;
var list = query
.AsExpandable()
.Select(v => new QuoteResult
{
Id = v.Id,
TimeZone = getTimeZone.Invoke(v),
Note = v.Note
// and about 10 more simple properties like v.Id above
})
.ToList();
return list;
}
public IQueryable<TQuote> GetQuotes(
EfExpressionPropertyDbContext ctx)
{
return ctx.Set<TQuote>();
}
}
class CommonQuoteHelper : QuoteHelper<CommonQuote>
{
protected override Expression<Func<CommonQuote, int?>> GetTimeZone
{
get { return q => q.Area != null ? (int?)q.Area.TimeZone : null; }
}
}
class PeculiarQuoteHelper : QuoteHelper<PeculiarQuote>
{
protected override Expression<Func<PeculiarQuote, int?>> GetTimeZone
{
get { return q => q.Region != null ? (int?)q.Region.RegionalTimeZone : null; }
}
}

How to create a reusable where clause for EF6

I have recently moved from coding in Java to c# and I am still learning the various elements of c#.
To access an existing database, which I cannot redesign, I am using Entity Frameworks 6 and 'Code First from database' to generate contexts and types representing the database tables. I am using Ling-To-SQL to retrieve the data from the database which is heavily denormalized.
My current task is create a report where each section is read from various tables, which all have a relationship to one base table.
This is my working example:
using(var db = new PaymentContext())
{
var out = from pay in db.Payment
join typ in db.Type on new { pay.ID, pay.TypeID } equals
new { typ.ID, typ.TypeID }
join base in db.BaseTable on
new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 } equals
new { base.Key1, base.Key2, base.Key3, base.Key4, base.Key5 }
where
base.Cancelled.Equals("0") &&
base.TimeStamp.CompareTo(startTime) > 0 &&
base.TimeStamp.CompareTo(endTime) < 1 &&
.
(other conditions)
.
group new { pay, typ } by new { typ.PaymentType } into grp
select new
{
name = grp.Key,
count = grp.Count(),
total = grp.Sum(x => x.pay.Amount)
};
}
There will be a large number of sections in the report and each section will generate a where clause which will contain the conditions shown. In some sections, the required data will be extracted from tables up to five levels below the BaseTable.
What I want to do is create a resuable where clause for each report section, to avoid a lot of duplicated code.
After a lot of searching, I tried to use the solution suggested here , but this has been superseded in Entity Framework 6.
How do I avoid duplicating code unnecessarily?
I did try to use the extension clauses you suggested, but my generated classes do not extend the BaseTable, so I had to explicitly define the link through the navigation property. As only a small number of tables will be common in the queries, I decided to apply the filters directly to each table as required. I will define these as required.
krillgar suggested moving to straight LINQ syntax, which seems like good advice. We intend to redesign our database in the near future and this will remove some of the SQL dependency. I merged the suggested filters and full LINQ syntax to access my data.
// A class to hold all the possible conditions applied for the report
// Can be applied at various levels within the select
public class WhereConditions
{
public string CancelledFlag { get; set; } = "0"; // <= default setting
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
// Class to define all the filters to be applied to any level of table
public static class QueryExtensions
{
public static IQueryable<BaseTable> ApplyCancellationFilter(this IQueryable<BaseTable> base, WhereConditions clause)
{
return base.Where(bse => bse.CancelFlag.Equals(clause.CancelledFlag));
}
public static IQueryable<BaseTable> ApplyTimeFilter(this IQueryable<BaseTable> base, WhereConditions clause)
{
return base.Where(bse => bse.TimeStamp.CompareTo(clause.StartTime) > 0 &&
bse.TimeStamp.CompareTo(clause.EndTime) < 1);
}
}
And the query is composed as follows:
using (var db = new PaymentContext())
{
IEnumerable<BaseTable> filter = db.BaseTable.ApplyCancellationFilter(clause).ApplyTimeFilter(clause);
var result = db.Payment.
Join(
filter,
pay => new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 },
bse => new { bse.Key1, bse.Key2, bse.Key3, bse.Key4, bse.Key5 },
(pay, bse) => new { Payment = pay, BaseTable = bse }).
Join(
db.Type,
pay => new { pay.Payment.TypeKey1, pay.Payment.TypeKey2 },
typ => new { typ.TypeKey1, typ.TypeKey2 },
(pay, typ) => new { name = typ.Description, amount = pay.Amount }).
GroupBy(x => x.name).
Select(y => new { name = y.Key,
count = y.Count(),
amount = y.Sum(z => z.amount)});
}
And then to finally execute composed query.
var reportDetail = result.ToArray(); // <= Access database here
As this query is the simplest I will have to apply, future queries will become much more complicated.
The nice thing about LINQ is that methods like Where() return an IEnumerable<T> that you can feed into the next method.
You could refactor the where clauses into extension methods akin to:
public static class PaymentQueryExtensions {
public static IQueryable<T> ApplyNotCancelledFilter(
this IQueryable<T> payments)
where T : BaseTable {
// no explicit 'join' needed to access properties of base class in EF Model
return payments.Where(p => p.Cancelled.Equals("0"));
}
public static IQueryable<T> ApplyTimeFilter(
this IQueryable<T> payments, DateTime startTime, DateTime endTime)
where T: BaseTable {
return payments.Where(p => p.TimeStamp.CompareTo(startTime) > 0
&& p.TimeStamp.CompareTo(endTime) < 1);
}
public static IGrouping<Typ, T> GroupByType(
this IQueryable<T> payments)
where T: BaseTable {
// assuming the relationship Payment -> Typ has been set up with a backlink property Payment.Typ
// e.g. for EF fluent API:
// ModelBuilder.Entity<Typ>().HasMany(t => t.Payment).WithRequired(p => p.Typ);
return payments.GroupBy(p => p.Typ);
}
}
And then compose your queries using these building blocks:
IEnumerable<Payment> payments = db.Payment
.ApplyNotCancelledFilter()
.ApplyTimeFilter(startTime, endTime);
if (renderSectionOne) {
payments = payments.ApplySectionOneFilter();
}
var paymentsByType = payments.GroupByType();
var result = paymentsByType.Select(new
{
name = grp.Key,
count = grp.Count(),
total = grp.Sum(x => x.pay.Amount)
}
);
Now that you have composed the query, execute it by enumerating. No DB access has happened until now.
var output = result.ToArray(); // <- DB access happens here
Edit After the suggestion of Ivan, I looked at our codebase. As he mentioned, the Extension methods should work on IQueryable instead of IEnumerable. Just take care that you only use expressions that can be translated to SQL, i.e. do not call any custom code like an overriden ToString() method.
Edit 2 If Payment and other model classes inherit BaseTable, the filter methods can be written as generic methods that accept any child type of BaseTable. Also added example for grouping method.

Entity Framework ObjectQuery.Include()

I have an object with two objects as properties (User, PrimaryNode), both could potentially be null, see below:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public Node PrimaryNode { get; set; }
public User User { get; set; }
}
I'm using Entity Framework 6 to populate the Item object and using chained includes to populate the PrimaryNode and User objects within it.
When the first chained Include has a null object then the whole object returns as null, for example:
using (var db = new MyContext())
{
var item = db.Items.Include(i => i.User).Include(n => n.PrimaryNode).FirstOrDefault(i => i.ItemId == id);
}
If in the above example i.User is null then the item variable is null. Whats the best way of populating both the sub-objects in a way that if a sub-object is null then the parent object and the other sub-object will still be populated?
I don't think your issue is due to the Include calls. According with the documentation:
This extension method calls the Include(String) method of the
IQueryable source object, if such a method exists. If the source
IQueryable does not have a matching method, then this method does
nothing.
In other words is going to be translated to:
var item = db.Items.Include("User").Include("PrimaryNode").FirstOrDefault(i => i.ItemId == id);
My question is, are you sure you have an Item with that id properly related with existing rows in Users and PrimaryNodes tables in your DB?. When you call Include method at the end is going to be translated to a join, so if the FK of your relationship doesn't match with the PK that reference, your query should not return what you are expecting.
Anyways, if you want to try another variant to load related properties you can use Explicit Loading:
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
context.Entry(item).Reference(p => p.PrimaryNode).Load();
context.Entry(item).Reference(p => p.User).Load();
I think it would be better if you use Lazy loading int his situation. Just make the User and PrimaryNode virtual:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public virtual Node PrimaryNode { get; set; }
public virtual User User { get; set; }
}
And then:
var db = new MyContext();
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
As others have mentioned, I think your issue is not due to the Includes. However, I think the following method has value. It is functionally equivalent to what you are already doing with the chained includes, but I think it has several benefits including making the intention of the code clear to the user.
The includes can be placed in Extension methods:
using System.Data.Entity;
using System.Linq;
namespace Stackoverflow
{
public static class EntityExtensions
{
public static IQueryable<Item> IncludePrimaryNode(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.PrimaryNode);
}
public static IQueryable<Item> IncludeUser(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.User);
}
}
}
Then, you can use the extensions as follows:
using (var db = new MyContext())
{
var itemQuery = db.Items.IncludeUser();
itemQuery = itemQuery.IncludePrimaryNode();
var item = itemQuery.FirstOrDefault(i => i.Id == 1);
}
It's just another way of doing the same thing, but I like the clarity it adds to the code.

Pass Linq Expression to a function

I want to pass a property list of a class to a function. with in the function based on property list I'm going to generate a query. As exactly same functionality in Linq Select method.
Here I'm gonna implement this for Ingress Database.
As an example,
in front end I wanna run a select as this,
My Entity Class is like this
public class Customer
{
[System.Data.Linq.Mapping.ColumnAttribute(Name="Id",IsPrimaryKey=true)]
public string Id { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Name")]
public string Name { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Address")]
public string Address { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Email")]
public string Email { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Mobile")]
public string Mobile { get; set; }
}
I wanna call a Select function like this,
var result = dataAccessService.Select<Customer>(C=>C.Name,C.Address);
then,using result I can get the Name and Address properties' values.
I think my Select function should looks like this,
( *I think this should done using Linq Expression. But im not sure what are the input parameter and return type. * )
Class DataAccessService
{
// I'm not sure about this return type and input types, generic types.
public TResult Select<TSource,TResult>(Expression<Func<TSource,TResult>> selector)
{
// Here I wanna Iterate through the property list, which is passed from the caller.
// Here using the property list,
// I can get the ColumnAttribute name value and I can generate a select query.
}
}
This is a attempt to create a functionality like in Linq. But im not an expert in Linq Expressions.
There is a project call DbLinq from MIT, but its a big project and still i couldn't grab anything helpful from that.
Can someone please help me to start this, or can someone link me some useful resources to read about this.
What you're trying to do is creating a new anonymous type that consists of Name and Address. This is easily achievable via long form linq (I made that term up, for lack of a better explanation.) Here's a sample from Microsoft, link provided below:
public void Linq11()
{
List<Product> products = GetProductList();
var productInfos =
from p in products
select new { p.ProductName, p.Category, Price = p.UnitPrice };
Console.WriteLine("Product Info:");
foreach (var productInfo in productInfos)
{
Console.WriteLine("{0} is in the category {1} and costs {2} per unit.", productInfo.ProductName, productInfo.Category, productInfo.Price);
}
}
Details: Linq Select Samples
Update:
So are you trying to do something like this then?
var result = dataAccessService.Select<Customer>(c => c.Name, c => c.Address);
public object[] Select<TSource>(params Expression<Func<TSource, object>>[] selectors)
{
var toReturn = new object[selectors.Count()];
foreach (var s in selectors)
{
var func = s.Compile();
//TODO: If you implement Select a proper extension method, you can easily get the source
toReturn[i] = func(TSource);
}
return toReturn;
}
I don't understand why you're trying to implement Select as a function of DataAccessService? Are trying to create this as an extension method rather?
If this is not what you mean though, you need to rephrase you're question big time and as one commenter suggested, tell us what you need not how you want us to design it.

Reusing Linq to Entities' Expression<Func<T, TResult> in Select and Where calls

Suppose I have an entity object defined as
public partial class Article
{
public Id
{
get;
set;
}
public Text
{
get;
set;
}
public UserId
{
get;
set;
}
}
Based on some properties of an Article, I need to determine if the article can be deleted by a given user. So I add a static method to do the checking. Something like:
public partial class Article
{
public static Expression<Func<Article, bool>> CanBeDeletedBy(int userId)
{
//Add logic to be reused here
return a => a.UserId == userId;
}
}
So now I can do
using(MyEntities e = new MyEntities())
{
//get the current user id
int currentUserId = 0;
e.Articles.Where(Article.CanBeDeletedBy(currentUserid));
}
So far so good. Now I want to reuse the logic in CanBeDeletedBy while doing a Select, something like:
using(MyEntities e = new MyEntities())
{
//get the current user id
int currentUserId = 0;
e.Articles.Select(a => new
{
Text = a.Text,
CanBeDeleted = ???
};
}
But no matter what I try, I can't use the expression in the select method. I guess that If I can do
e.Articles.Select(a => new
{
Text = a.Text,
CanBeDeleted = a => a.UserId == userId
};
Then I should be able to use the same expression. Tried to compile the expression and call it by doing
e.Articles.Select(a => new
{
Text = a.Text,
CanBeDeleted = Article.CanBeDeletedBy(currentUserId).Compile()(a)
};
but it won't work either.
Any ideas on how to get this to work? Or if it isn't possible, what are the alternatives to reuse business logic in both places?
Thanks
Pedro
Re-using expression trees is a black art; you can do it, but you would need to switch a lot of code to reflection and you'd lose all the static checking. In particular, working with the anonymous types becomes a nightmare (although dynamic in 4.0 might be workable).
Further, if you cheat and use Expression.Invoke, then it isn't supported by all providers (most noticeably not on EF in .NET 3.5SP1).
Unless this is a major pain point, I'd leave it with duplication. Or do you need to re-use the expression tree?
What I did is I used PredicateBuilder which is a class in LinqKit and also AsExpandable() http://www.albahari.com/nutshell/linqkit.aspx to build up expressions and I stored them Statically as
public readonly Expression<Func<T,bool>>
in a static class. Each expression was building on a previous expression thus reducing the amount of duplication.
As the previous question by Marc Gravell suggests this kinda thing is a black art, but thankfully a lot of the work has been done by other poeple.

Categories

Resources