EF Core Expression in Select with multiple parameter - c#

So I have a property that I will parse into the Select method. This will work with one parameter but can I make it work with two, and if I can't what would be the approach? I am using EF Core 3.1.8. with the SqlServer 3.1.8 package.
private static Expression<Func<ClassOne, bool, ClassTwo> Summary
{
get
{
return (p, myBool) => new ClassTwo()
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
}
}
}
This is my Expression. I query with this method.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable // type of DbSet<ClassOne>
.Select(Summary) // how do I parse isAdmin to Summary?
.ToListAsync();
}
So I hope you can see my problem. I want to avoid the where clause in the method because I have at least 10 other methods that use this Expression in different ways and also in my case it would become an nested Where which is not possible which straight querying. I don't want C# to do the work for me, let SQL Server handle that.
Thanks in advance!
EDIT:
I tried this in the GetSummaryAsync but it is not possible:
.Select(i => (i, isAdmin))

Define extension method
public static class Extensions {
public static IQueryable<ClassTwo> Summary(this IQueryable<ClassOne> one, bool myBool)
{
return one.Select(p => new ClassTwo
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
});
}
}
And use it like this.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable
.Summary(isAdmin)
.ToListAsync();
}

Related

Trying to understand my IEnumerable and why I can't use .ToList()

I currently have the following method for one of my data objects repo patterns:
public async Task<IEnumerable<DropDownList>> GetDropDownListNoTracking()
{
return await context.TouchTypes
.AsNoTracking()
.Where(s => s.IsActive)
.Select(s => new DropDownList()
{
Id = s.Id,
Name = s.Description
}).ToListAsync();
}
When I call it in my page view :
private IList<DropDownList> TouchTypeList { get; set; }
private async Task LoadDropDownAsync()
{
TouchTypeList = await _unitOfWork.TouchType.GetDropDownListNoTracking();
}
I'm trying to understand why I can't just do a GetDropDownListNoTracking().ToList()
but instead, it wants me to cast : (IList<DropDownList>).
I can easily just change the property to fix this but I would assume .ToList would work here?
I'm mostly just trying to understand this so I can do it the correct way.
GetDropDownListNoTracking returns Task<IEnumerable<DropDownList>>, not IEnumerable<DropDownList>, so you'd have to do:
private async Task LoadDropDownAsync()
{
TouchTypeList = (await _unitOfWork.TouchType.GetDropDownListNoTracking()).ToList();
}
Calling ToList on an enumerable that you know is really always a list is a waste of CPU cycles, and points to a poor choice of return type for your method. The simplest solution is to change your method to:
public async Task<IList<DropDownList>> GetDropDownListNoTracking()
{
return await context.TouchTypes
.AsNoTracking()
.Where(s => s.IsActive)
.Select(s => new DropDownList()
{
Id = s.Id,
Name = s.Description
}).ToListAsync();
}
Although I personally would use IReadOnlyList instead.

Getting mongodb documents but ToListAsync on IQueryable throws exception

I am using MongoDB.Driver 2.10.4
I want to get all documents that have an id in a list of ids that I get from my controller
I am using this code :
var pending_processes = mongo_context.collectionName
.AsQueryable()
.Where(x => request.ids.Contains(x.Id))
.Select(x => new ViewModel()
{
process_id = x.Id,
// date = not important just accessing the x object
state = new States()
{
timeline = x.states.timeline,
part = x.states.part,
}
})
.ToList();
It works fine but if I make my function async and do an await and replace ToList() with ToListAsync() I get the following error:
The source IQueryable doesn't implement IAsyncEnumerable<Application.Process.Query.GetPendingProcesses.ViewModel>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Clearly there is something I am not getting here my concern is I don't want my code to run synchronously this would be really bad. usually when dealing with postgresql context I always use the ToListAsync() but here in order to use linq with mongo I had to use AsQueryable() and AsQueryable() as I understand it does not get the data it's a normal query that I need to execute afterwards but when I use with it ToList() everything works but when I use ToListAsync() I get the error.
I just want to know what is behind all of this and is the code above synchronous or asynchronous?
I just changed my query using Find and ForEachAsync() and now all works well. I just did not use AsQueryable() because the mongodb driver as I understand it use these other functions but provides a way to use linq so I used the default methods without linq-
var result = new GetPendingProcessesViewModel();
var filter = Builders<Domain.MongoDocuments.Process>
.Filter.Where(x => own_processes.Contains(x.Id));
await _elba_mongo_context.process
.Find(filter)
.ForEachAsync(x =>
result.pending_processes_own.Add(
new GetPendingProcessesViewModelItem()
{
process_id = x.Id,
state = new States()
{
timeline = x.states.timeline,
part = x.states.part,
supplier = x.states.supplier
}
}
)
);
You can get the documentation and references for your MongoDB driver version from GitHub.
Sure you can keep it asynchronous, but first you have switch out AsQueryable to some other method which returns back and IQueryable.
In a nutshell ToListAsync() works on a IQueryable<T> only, when you turned it in to a IEnumerable via AsEnumerable() you lost the ability to call it. Its explained well here
You have a couple of choices, either implement IDbAsyncEnumerable see here or change the result list you have into an async list with Task.FromResult()
Option 1:
// try this in your controller
public async Task<List<PeopleStatesType>> GetAsyncStatesList()
{
//for e.g.
List<PeopleType> peopleList = new List<PeopleType>()
{
new PeopleType(){ Name = "Frank", Gender = "M" },
new PeopleType(){ Name = "Rose", Gender = "F" } //..
};
var result = from e in peopleList
where e.Gender == "M"
select e;
return await Task.FromResult(result.ToList());
}
Option 2:
Use this class
public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable) {
}
public AsyncEnumerableQuery(Expression expression) : base(expression) {
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator() {
return new InMemoryDbAsyncEnumerator<T>(((IEnumerable<T>) this).GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() {
return GetAsyncEnumerator();
}
private class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
private readonly IEnumerator<T> _enumerator;
public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator) {
_enumerator = enumerator;
}
public void Dispose() {
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken) {
return Task.FromResult(_enumerator.MoveNext());
}
public T Current => _enumerator.Current;
object IDbAsyncEnumerator.Current => Current;
}
}
// FindAll: with a condition like .Find(x => x.user == "Jone Doe")
// see [here][3]
var task = collection.Find(p => true).ToListAsync();
Update Option 3: Simple Async Get
public async Task<IEnumerable<MyMongoEntity>> Where(Expression<Func<MyMongoEntity, bool>> expression = null)
{
return await context.GetCollection<MyMongoEntity>(typeof(MyMongoEntity).Name, expression).Result.ToListAsync();
}
Based on your comment, for a simple get documents collection, this helper should work.
From your error, it seems like the mongo_context.collectionName is returning something from Entity Framework?
Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Make sure you are calling the AsQueryable extension method directly on the Mongo collection. (Your code just shows mongo_context.collectionName.AsQueryable() so I'm not sure you're doing that)
Hooking into the LINQ provider requires getting access to an IQueryable instance. The driver provides an AsQueryable extension method on IMongoCollection.
var collection = db.GetCollection<Person>("people");
var queryable = collection.AsQueryable();
Reference: https://mongodb.github.io/mongo-csharp-driver/2.10/reference/driver/crud/linq/#queryable
The AsQueryable extension above actually returns an IQueryable instance that implements IMongoQueryable and has all the same async extensions that other ORMs (Entity Framework, NHibernate, etc.) have - including ToListAsync.

asp.net is there a way to get id in list?

I have a many-to-many between 'Edital' and 'Graduando' and I created a entity called 'EditalGraduando'. Now I want to know every 'edital' that 'graduando' subscribe using the graduando id. So I did this:
public IQueryable<int> GetGraduandosIds(int editalId)
{
var graduandosId = db.EditalGraduandoe.Where(e => e.EditalId == editalId).Select(i => i.graduandoID);
return graduandosId;
}
There's a way to do a select in the entety 'Graduando' using this result? Like this: SQL WHERE ID IN (id1, id2, ..., idn)
Yes, you can do this:
public IQueryable<Graduandoe> GetGraduandos(IEnumerable<int> graduandosIds)
{
return db.Graduandoe.Where(g => graduandosIds.Contains(g.graduandoID));
}
Alternatively, you could have navigation properties, and you can write the following instead of the two functions:
public IQueryable<Graduandoe> GetGraduandos(int editalId)
{
return db.EditalGraduandoe.Where(e => e.EditalId == editalId).Select(i => i.Graduandoe);
}

Refactor Linq code and "LINQ to Entities does not recognize the method"

I am trying to refactor Linq to Entity query to prevent writing duplicate code but I can't avoid a "LINQ to Entities does not recognize the method" exception. The reason is IsUserActive method.
I am providing the code below as example
public static bool IsUserActive(this User user)
{
return user.Orders.Any(c => c.Active && (c.TransactionType == TransactionType.Order || c.TransactionType == TransactionType.Subscription));
}
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active =p.IsUserActive()
});
}
Is it possible to refactor Linq code to prevent duplication in my situation?
I can't use IList instead of IQueryable.
I know why this exception happens, no need to explain.
If you want to use your method in a lot of places, I don't think you can do anything except store it as an expression and then referring to that expression wherever you were calling the original method:
public static Expression<Func<User, bool>> IsUserActive = user =>
user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription));
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active = IsUserActive(p)
});
}
EDIT: OK, so this doesn't work. You can't just "call" an expression like this. The only alternative I can think of is to turn your whole Select parameter into an expression:
public static Expression<Func<User, UserView>> UserToUserView = user =>
new UserView
{
Id = user.Id,
Active = user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription)
};
public IQueryable<UserView> GetView()
{
return context.users.Select(UserToUserView);
}
This is little tricky, but this is how we are doing it. You can combine multiple IQueryables into one as shown below.
public static IQueryable<Order> ActiveOrdersQuery(this MyDBEntities db)
{
return db.orders.Where(
o=> o.Active &&
(o.TransactionType == TransactionType.Order ||
o.TransactionType == TransactionType.Subscription )));
}
public IQueryable<UserView> GetView()
{
var q = context.ActiveOrdersQuery();
return context.users.Select( x=> new UserView{
Id = x.Id,
Active = q.Any( o=> o.UserId == x.Id)
});
}
The only problem is you have to explicitly compare foreign keys. But this results in exactly same SQL and will perform same.
This way you can refactor common queries and use them inside your views.

How to pass a Lambda Expression as method parameter with EF

How do I pass an EF expression as a method parameter?
To illustrate my question I have created a pseudo code example:
The first example is my method today. The example utilizes EF and a Fancy Retry Logic.
What I need to do is to encapsulate the Fancy Retry Logic so that it becomes more generic and does not duplicate.
In the second example is how I want it to be, with a helper method that accepts the EF expression as a parameter.
This would be a trivial thing to do with SQL, but I want to do it with EF so that I can benefit from the strongly typed objects.
First Example:
public static User GetUser(String userEmail)
{
using (MyEntities dataModel = new MyEntities ())
{
var query = FancyRetryLogic(() =>
{
(dataModel.Users.FirstOrDefault<User>(x => x.UserEmail == userEmail)));
});
return query;
}
}
Second Example:
T RetryHelper<T>(Expression<Func<T, TValue>> expression)
{
using (MyEntities dataModel = new (MyEntities ())
{
var query = FancyRetryLogic(() =>
{
return dataModel.expression
});
}
}
public User GetUser(String userEmail)
{
return RetryHelper<User>(<User>.FirstOrDefault<User>(x => x.UserEmail == userEmail))
}
Maybe something like this?
public TValue RetryHelper<T, TValue>(Func<ObjectSet<T>, TValue> func)
where T : class
{
using (MyEntities dataModel = new MyEntities())
{
var entitySet = dataModel.CreateObjectSet<T>();
return FancyRetryLogic(() =>
{
return func(entitySet);
});
}
}
public User GetUser(String userEmail)
{
return RetryHelper<User, User>(u => u.FirstOrDefault(x => x.UserEmail == userEmail));
}
Just to post what we discussed already...
Here is a link that might help you, I think it has something similar to what you need.
Using AsQueryable With Linq To Objects And Linq To SQL
How do I cache an IQueryable object?
I've seen better examples but I don't have them handy, basically as I mentioned you can use that to keep your query in a form so that you can further filter, change until the very last moment when you know all is done and can actually realize and enumerate the query.
hope it helps

Categories

Resources