Union on empty Enumerable - c#

I have function
public async Task<IQueryable<Document>> GetDocuments(...)
in which I search for documents under some given conditions. Some conditions can be skipped. At the end I perform union of these queries.
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
documents = myDocs.Union(wards).Union(groups);
}
}
But if one of these partial queries is empty (was skipped) when I try obtain these documents in way shown below I got error.
var documentsPaginated = await documents.Skip(param.Start)
.Take(param.Length)
.ToListAsync();
Error: The source IQueryable doesn't implement IDbAsyncEnumerable.
How can I make this function to be able to skip some sub queries and then union all. I would prefer not to change function return value.

Try this, althought your code seems to have a massive amount of code smell...
public async Task<IQueryable<Document>> GetDocuments(...)
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = null;
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = null;
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = null;
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
if(myDocs != null)
documents = documents.Union(myDocs);
if(wards != null)
documents = documents.Union(wards);
if(groups != null)
documents = documents.Union(groups);
}
}

It appears that ToListAsync can't be used interchangeably with linq, but only in an EF query.
Quote from MSDN
Entity Framework 6 introduced a set of extension methods that can be used to asynchronously execute a query. Examples of these methods include ToListAsync, FirstAsync, ForEachAsync, etc.
Because Entity Framework queries make use of LINQ, the extension methods are defined on IQueryable and IEnumerable. However, because they are only designed to be used with Entity Framework you may receive the following error if you try to use them on a LINQ query that isn’t an Entity Framework query.
The source IQueryable doesn't implement IDbAsyncEnumerable{0}. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

Related

Nhibernate: Conditional And Clause in QueryOver()

this is my query:
var query = _session.QueryOver<CRMPhoneCall>();
if (filter.Subject != null)
{
query.JoinQueryOver<CRMPhoneCall_Subject>
(p => p.PhoneCall_Subject)
.JoinQueryOver(d => d.Subject)
.And(s => s.Id == filter.Subject.Id) //check filter.Subject.Id has value else The condition does not apply
.And(s => s.Parent == null) //check filter.OnlyMainSubject has true else The condition does not apply;
}
two separate if for check filter.Subject And filter.OnlyMainSubject does not work(duplicate instance in QueryOver)
var query = session.QueryOver<CRMPhoneCall>();
if (filter.Subject != null)
{
var subjectQuery = query.JoinQueryOver<CRMPhoneCall_Subject>(p => p.PhoneCall_Subject)
.Where(s => s.Subject == filter.Subject);
if (filter.OnlyMainSubject)
{
subjectQuery.JoinQueryOver(ps => ps.Subject)
.Where(s => s.Parent == null); //check filter.OnlyMainSubject has true else The condition does not apply;
}
}

Updating multiple rows with different data with a specific id using Entity Framework Core

I have the following table:
I want to update ApproveStatus of rows with RequestId=2 where the goodsId = 4 is rejected and goodsId=1 is approved.
My question is how to do that using Entity Framework Core in ASP.NET Core 2.2.
This is what I have done so far:
// model contain "approve" and "reject" strings and requestId which came from view
public IActionResult ApproveList(List<ApprovedGoodsByAdminAssistVM> model)
{
// I got the requestId from model
var id = model.Select(s => s.RequestId).FirstOrDefault();
// Filtered the two record with RequestId=2 from the table
var goodsList = context.RequestGoodsBrand
.Where(x => x.RequestId == id).ToList();
}
What to do next?
Thanks in advance
If I'm not mistaken, these changes could solve your issue.
public IActionResult ApproveList(List<ApprovedGoodsByAdminAssistVM> model)
{
var id = model.Select(s => s.RequestId).FirstOrDefault();
var goodsList = context.RequestGoodsBrand
.Where(x => x.RequestId == id).ToList();
var rejectedItems = goodsList.Where(x => x.GoodsId == 4).ToList();
var approvedItems = goodsList.Where(x => x.GoodsId == 1).ToList();
foreach (var item in rejectedItems)
item.ApproveStatus = "rejected";
foreach (var item in approvedItems)
item.ApproveStatus = "approved"
context.SaveChanges();
}
Just instead of these magic words ('Rejected' , 'Approved'), you would better use an enum
public IActionResult ApproveList(List<ApprovedGoodsByAdminAssistVM> model)
{
var requestId = model.Select(x => x.RequestId).FirstOrDefault();
var approvedItems = model
.Where(x => x.RequestId == requestId && x.ApproveStatus == 1)
.Select(x => x.GoodsId).ToList();
var rejectedItems = model
.Where(x => x.RequestId == requestId && x.ApproveStatus == 0)
.Select(x => x.GoodsId).ToList();
var goodsToBeApproved = context.RequestGoodsBrand
.Where(x => x.RequestId == requestId && approvedItems.Contains(x.GoodsId)).ToList();
goodsToBeApproved.ForEach(x => x.IsApproved = 1);
context.SaveChanges();
var goodsToBeRejected = context.RequestGoodsBrand
.Where(x => x.RequestId == requestId && rejectedItems.Contains(x.GoodsId)).ToList();
goodsToBeRejected.ForEach(x => x.IsApproved = 0);
context.SaveChanges();
return Json("Done!");
}

LINQ method Except gives me System.ArgumentException for IQuareable

I have the following code for filtering my data:
public List<Customer> ShowCustomersForCurrentTemplate(PushTemplate pushTemplate)
{
string templateType = pushTemplate.TemplateType;
string timeType = pushTemplate.IntervalType;
DateTime clientStartDate = pushTemplate.ClientStartDateTime;
int eventOlderThan = pushTemplate.EventOlderThan;
int eventYoungerThan = pushTemplate.EventYoungerThan;
List<string> langs = pushTemplate.Messages?.Select(x => x.PushLang?.LangCode).ToList() ??
new List<string>();
List<string> packageIds = pushTemplate.PackageIds ?? new List<string>();
List<string> advertisers = pushTemplate.AdvertiserPushTemplates?.Select(x => x.Advertiser?.Name).ToList() ??
new List<string>();
string category = pushTemplate.Category?.Name;
DateTime eventOlderThanDate = DateTime.UtcNow;
DateTime eventYoungerThanDate = DateTime.UtcNow;
if (timeType == "days")
{
eventOlderThanDate = eventOlderThanDate.AddDays(0 - eventOlderThan);
eventYoungerThanDate = eventYoungerThanDate.AddDays(0 - eventYoungerThan);
}
else if (timeType == "minutes")
{
eventOlderThanDate = eventOlderThanDate.AddMinutes(0 - eventOlderThan);
eventYoungerThanDate = eventYoungerThanDate.AddMinutes(0 - eventYoungerThan);
}
var customers = _customerRepository.Customers
.Include(x => x.Application)
.ThenInclude(x => x.Advertiser)
.ThenInclude(x => x.Category)
.Include(x => x.CustomerEvents)
.Select(x => x);
// selection by category
customers = customers.Where(x => x.Application.Advertiser.Category.Name == category);
// selection by advertisers
customers = customers.Where(x => advertisers.Contains(x.Application.Advertiser.Name));
// selection by applications
customers = customers.Where(x => packageIds.Contains(x.Application.AppId));
// selection by locales
customers = customers.Where(x => langs.Contains(x.Locale));
if (templateType == "registration_and_not_deposit")
{
// selection by event type
customers = customers.Where(x => x.CustomerEvents
.Any(y => y.Customer.CreatedAt > clientStartDate && y.CreatedAt <= eventOlderThanDate &&
y.CreatedAt >= eventYoungerThanDate && y.EventType == "registration"));
// set of redundant events
var customersWithEventsForException =
customers.Where(x => x.CustomerEvents.Any(y => y.EventType == "deposit"));
// filter redundant events
customers = customers.Except(customersWithEventsForException);
}
else if (templateType == "opened_and_not_registration")
{
// отбор по типу события
customers = customers.Where(x => x.CustomerEvents
.Any(y => y.Customer.CreatedAt > clientStartDate && y.CreatedAt <= eventOlderThanDate &&
y.CreatedAt >= eventYoungerThanDate && y.EventType == "open"));
// selection be event
var customersWithEventsForException =
customers.Where(x => x.CustomerEvents.Any(y => y.EventType == "registration"));
// filter redundant events
customers = customers.Except(customersWithEventsForException);
}
// set of users who already had push
var alreadyHavePush = _pushRepository.Pushes
.Include(x => x.Customer)
.Where(x => x.PushTemplate.PushTemplateId == pushTemplate.PushTemplateId && x.Sent)
.Select(x => x.Customer);
// filter users, who already had push
var res = customers.Except(alreadyHavePush).Select(x => x).ToList();
return res;
}
Sorry for long method, but it's pretty simple. A make Where for IQueryable again and again then I use .ToList() for getting dinally filtered result.
Ok about the problem. It seems to me, that Except method gives me
System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable1[Microsoft.EntityFrameworkCore.Query.Internal.AnonymousObject]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable1[System.Object]' of method 'System.Collections.Generic.IEnumerable1[System.Object] Except[Object](System.Collections.Generic.IEnumerable1[System.Object], System.Collections.Generic.IEnumerable`1[System.Object])'
I will show you this moment with debugger.
The first case. Here all is ok:
The second case:
I will not have kek2 because the described exception will be raised.
So what is wrong? It's impossible to use Except for IQuareable?
Here I will show the source code of customers repository:
public class CustomerRepository : ICustomerRepository
{
private readonly ApplicationDbContext _applicationContext;
public CustomerRepository(ApplicationDbContext applicationContext)
{
_applicationContext = applicationContext;
}
public IQueryable<Customer> Customers => _applicationContext.Customers;
...

EF Core: Distinct and OrderBy strange behavior

I migrate my old project to the new EF Core and found this problem
My old code:
private IQueryable<SeriesData> GetSeriesData(IQueryable<Series> query, long? userId = null)
{
DateTime date = DateTime.Today.AddMonths(1);
IQueryable<SeriesData> seriesDataQuery = query.Select(x => new SeriesData
{
Series = x,
Subscribed = userId.HasValue && x.Subscriptions.Any(y => y.UserId == userId),
CurrentSeasonNumber = x.Episodes.Where(z => z.ReleaseDate.HasValue && z.ReleaseDate < date).Max(y => y.SeasonNumber),
Channel = x.Channel,
Country = x.Channel.Country,
ReleaseGroups =
x.Episodes.SelectMany(z => z.Releases)
.Select(y => y.ReleaseGroup)
.Distinct() // 1
.OrderBy(y => y.Name) // 2
.Select(r => new ReleaseGroupData
{
ReleaseGroup = r,
Subscribed =
userId.HasValue &&
x.Subscriptions.Any(y => y.UserId == userId && y.ReleaseGroupId == r.Id)
}).ToList()
});
return seriesDataQuery;
}
When i execute this query i get "InvalidOperationException: Sequence contains more than one element" exception
But if i swap line 1 and 2 everything works.

Select two columns from the table via linq

I use the query below to get all columns(20 more) in Entity Framework Linq. Because of out of memory exception, I only want to get two of them. One is "FileName", the other one is "FilePath". How to modify my code?
var query = DBContext.Table1
.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.FilePath)
.Skip(1000)
.Take(1000)
.ToList();
foreach(var t in query)
{
Console.WriteLine(t.FilePath +"\\"+t.FileName);
}
var query = DBContext.Table1.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.FilePath)
.Skip(1000)
.Take(1000)
.Select(c => new { c.FilePath, c.FileName })
.ToList();
foreach(var t in query)
{
Console.WriteLine(t.FilePath +"\\"+t.FileName);
}
You need to use Select.
Just select out two of the columns:
DBContext.Table1.Select(c => new { c.FileName, c.FilePath });
How about something like
using (var entity = new MyModel(ConnectionString))
{
var query = (from myTable in entity.theTable
where myTable.FacilityID == facilityID &&
myTable.FilePath != null &&
myTable.TimeStationOffHook < oldDate
orderby myTable.FilePath
select new
{
myTable,FileName,
myTable.FilePath
}).Skip(1000).Take(1000).ToList();
//do what you want with the query result here
}

Categories