EF Core 3 evaluate navigation property null on server - ef-core-3.0

I have a request
DbContext.Invoices
.Where(x => x.Status != InvoiceStatus.Draft && x.PaymentMethod == PaymentMethod.DirectDebit)
.Where(x => x.DirectDebitFile == null).ToList();
DirectDebitFile is a reverse navigation property.
Which was working fine in EF Core 2, not sure about how it was evaluated in the final request.
After upgrade to EF Core 3, this request doesn't work anymore and says
System.InvalidOperationException: The LINQ expression 'DbSet<Invoice>
.Where(i => !(i.IsDeleted))
.Where(i => i.ClubId == __CurrentUser_ClubId_0)
.Cast()
.Where(e => e.FederationId == __CurrentUser_FederationId_1)
.Cast()
.Where(e0 => !(e0.Hidden))
.Cast()
.Where(e1 => (int)e1.Status != 0 && (Nullable<int>)e1.PaymentMethod == (Nullable<int>)DirectDebit)
.LeftJoin(
outer: DbSet<DirectDebitFile>
.Where(d => !(d.IsDeleted)),
inner: e1 => EF.Property<Nullable<long>>(e1, "Id"),
outerKeySelector: d => EF.Property<Nullable<long>>(d, "InvoiceId"),
innerKeySelector: (o, i) => new TransparentIdentifier<Invoice, DirectDebitFile>(
Outer = o,
Inner = i
))
.Where(e1 => e1.Inner == null)' 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.
I can rewrite this query and make it work by moving evaluation to the client-side
DbContext.Invoices
.Include(x=>x.DirectDebitFile)
.Where(x => x.Status != InvoiceStatus.Draft && x.PaymentMethod == PaymentMethod.DirectDebit)
.AsEnumerable()
.Where(x => x.DirectDebitFile == null).ToList();
But in this case, of course, the query will pull up all the rows and filtering x.DirectDebitFile == null will be on the client-side. I want this query to be evaluated on the server, please help to achieve that.

Currently, I changed the request as the way as normal SQL check it, by checking one of the joined table field
AppDbContext.Invoices.Include(x => x.DirectDebitFile)
.Where(x => x.Status != InvoiceStatus.Draft)
.Where(x => x.DirectDebitFile.FileName == null);

Related

Entity Framework fuzzy search on list of strings

EntityFrameworkCore 3.1.14
So i'm looking to implement a search where you can add multiple different elements to a search which will pull the relevant data from the DB (including lists of search params).
I'm fairly new to EF, but couldn't find anything on this..
The requirement is this:
if the list is empty - don't filter (there might be other search params)
if the list contains some strings, do a fuzzy search - i.e. string.Contains(string2)
it works in code - e.g.
var list = new List<string> { "aaa", "bbb" };
var a = new List<string> { "a" };
var c = new List<string> { "c" };
var ab = list.Any(listString => a.Any(aString => listString.Contains(aString))); //returns true
var ac = list.Any(listString => c.Any(cString => listString.Contains(cString))); //returns false
However, it doesn't work in entityframework.. i have tried using the below (only 1 of the lines in the WHERE at a time - result of the search is at the end of the row)
var data = context.Meals
.Where(x =>
(!descriptions.Any() || x.Ingredients.Any(db => descriptions.Any(input => input.Contains(db.Description)))) --FAIL
(!descriptions.Any() || x.Ingredients.Any(db => descriptions.Any(input => db.Description.Contains(input)))) --FAIL
(!descriptions.Any() || descriptions.Any(input => x.Ingredients.Any(db => db.Description.Contains(input)))) --FAIL
(!descriptions.Any() || descriptions.Any(input => x.Ingredients.Any(db => input.Contains(db.Description)))) --FAIL
(!descriptions.Any() || x.Ingredients.Any(db => db.Description.Contains(descriptions.First()))) --PASS - but restricted to using .First()
(!descriptions.Any() || x.Ingredients.Any(db => descriptions.Contains(db.Description))) --PASS - but no fuzzy logic
).ToList();
ERROR MESSAGE:
The LINQ expression ... 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().
i found a solution - which is to loop through the list of strings outside EF and for each element, add a new where clause to the EF object..
However, this fix seems more like a workaround, than a proper implementation for using fuzzy search on 2 sets of strings
var data = context.Meals;
if(descriptions.Any()){
descriptions.ForEach(input => data = data.Where(x => x.Any(db => db.Ingredient.Contains(input))))
}
return data.ToList();

Automapper through relations in Entity Framework

As a newbie to automapper (v10.0.0) I'm trying to replace one of my queries. I currently use this to generate my response:
var query = from a in _context.ApprovalStatuses.AsNoTracking()
.Include(x => x.ApprovalOrder).ThenInclude(x => x.Worker)
where a.RequestId == request.Id
orderby a.ApprovalOrder.Position
let w = a.ApprovalOrder.Worker
select new RequestApprovalStatusDTO {
AssignedUtc = a.AssignedUtc,
Comments = a.Comments,
DecisionDateUtc = a.ApprovedDateUtc ?? a.RejectedDateUtc,
Email = w.Email,
Name = w.Name,
Uuid = a.Uuid
};
So I started by creating my mapping in my Profile subclass:
CreateMap<ApprovalStatus, RequestApprovalStatusDTO>()
.ForMember(x => x.DecisionDateUtc, x => x.MapFrom(y => y.ApprovedDateUtc ?? y.RejectedDateUtc))
.ForMember(x => x.Email, x => x.MapFrom(y => y.ApprovalOrder.Worker.Email))
.ForMember(x => x.Name, x => x.MapFrom(y => y.ApprovalOrder.Worker.Name));
And then I rewrote the query like so:
var query = _context.ApprovalStatuses
.Include(x => x.ApprovalOrder)
.ThenInclude(x => x.Worker)
.Where(x => x.RequestId == request.Id)
.OrderBy(x => x.ApprovalOrder.Position);
return Ok(_mapper.Map<RequestApprovalStatusDTO>(query));
At runtime, it's telling me
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
Object -> RequestApprovalStatusDTO
System.Object -> BoltOn.RequestApprovalStatusDTO
lambda_method(Closure , object , RequestApprovalStatusDTO , ResolutionContext )
I understand it's telling me that it doesn't know how to convert from object, but I'm not sure why it's trying to do that since query is an IOrderedQueryable<ApprovalStatus>.
Thanks to Lucian's pointer I was able to solve it like so:
var query = _context.ApprovalStatuses
.Where(x => x.Request.Uuid == uuid)
.OrderBy(x => x.ApprovalOrder.Position);
var approvals = await _mapper.ProjectTo<RequestApprovalStatusDTO>(query).ToArrayAsync();
if (approvals.Length == 0)
return NotFound();
return Ok(approvals);

ServiceStack Ormlite SqlExpressionVisitor null check in Where extension

I would like to write a method for querying table with one method by null cheking parameters using SqlExpressionVisitor of Ormlite
Here is my method :
public static List<UserChatsDTO> GetUserChats(int startRow, int rowCount, DateTime? startDate, DateTime? endDate, string operatorName, short? rating, string visitorName)
{
using (IDbConnection db = DbFactory.OpenDbConnection())
{
SqlExpressionVisitor<UserChatsDTO> ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<UserChatsDTO>();
ev.Where(q =>
(startDate.HasValue && q.Dated >= startDate) &&
(endDate.HasValue && q.Dated <= endDate) &&
(!string.IsNullOrEmpty(operatorName) && q.TakenByUser.Contains(operatorName)) &&
(rating.HasValue && q.Rating == (short)rating) &&
(!string.IsNullOrEmpty(visitorName) && q.VisitorName.Contains(visitorName)));
//ev.OrderBy();
ev.Limit(startRow, rowCount);
return db.Select<UserChatsDTO>(ev);
}
}
But Object reference not set to an instance of an object. NullReferenceException is thrown when i call ev.Where part.
Is there a bug here or i am missing something ?
Thank you.
You can actually build up the ExpressionVisitor inside the Select method like so:
var chats = db.Select<UserChatsDTO>(q => q
.Where(x => startDate.HasValue && x.Date >= startDate)
.Where(x => endDate.HasValue && x.Date <= endDate)
.Where(x => string.IsNullOrEmpty(operatorName) || x.TakeByUser.Contains(operatorName))
.Where(x => rating.HasValue && x.Rating == (short)rating)
.Where(x => string.IsNullOrEmpty(visitorName) || x.VisitorName.Contains(visitorName)
.Limit(startRow, rowCount));
I know this question is 7 months old but I had a similar issue & this was the first question that came up when I searched. I wanted to add my working solution since Master Morality's didn't work for me.
Originally, I tried syntax roughly similar to mustafasturan's first attempt. I got the same NullReferenceException he did. Master Morality's answer did not help either...
I'm trying to build a search function that performs LIKE searches rather than exact match. There are multiple criteria on the request object which may or may not be null (for simplicity's sake we'll use an example with 2 criteria). Following Master Morality's suggestion, I tried this:
var searchResults = db.Select<Item>(q => q
.Where(x => string.IsNullOrWhiteSpace(request.Criteria1) || x.Criteria1.Contains(request.Criteria1))
.Where(x => string.IsNullOrWhiteSpace(request.Criteria2) || x.Criteria2.Contains(request.Criteria2))
);
I still got a NullReferenceException. Seems like the && and || operators are not using short-circuit evaluation inside the lambda expression.
What I finally had to go with is this:
SqlExpressionVisitor<Item> ev = new ServiceStack.OrmLite.MySql.MySqlExpressionVisitor<Item>();
if (!String.IsNullOrWhiteSpace(request.Criteria1)) {
ev.Where(q => q.Criteria1.Contains(request.Criteria1));
}
if (!String.IsNullOrWhiteSpace(request.Criteria2)) {
ev.Where(q => q.Criteria2.Contains(request.Criteria2));
}
searchResults = db.Select<Item>(ev);
It doesn't feel very elegant, but it's the only thing I could find that works.

Performance issue : How to execute Two lambda expression at once?. "Contains" and "Any" operator used

Sample code
var Ids = _db.Projects.Where(Project=>Project.Title!="test23rdoct")
.Select (pro => pro.Id);
Expression<Func<Company, bool>> masterExpression =
Company => Company.Participants.Any(part => ids.Contains(part.Project.Id));
IQueryable<Object> queryEntity = _db.Companies.Where(masterExpression)
The above query executing twice. Storing ids in the server(sometime ids are more than 50k count). It causes performance issues. Could anybody suggest how to combine these two queries and execute at once?
How about:
var queryEntity = _db.Companies.Where(c => c.Partipants.Any(p => p.Project.Title != "test23rdoct"));
EDIT:
With the complex query, you could also split that:
Func<Project, bool> projectFilter = Project => ((Compare(Convert(Project.Title), "a") > 0) AndAlso ((Convert(Project.Title) != "test23rdoct") AndAlso
(Project.Participants.Any(Participant => (Compare(Convert(Participant.ParticipantRole.Name), "Finance") > 0)) AndAlso
(Project.Participants.Any(Participant => (Convert(Participant.Person.FirstName) != "test1")) AndAlso
Project.Participants.Any(Participant => (Compare(Convert(Participant.Company.Name), "test") > 0))))));
And then do:
var queryEntity = _db.Companies.Where(c => c.Partipants.Any(p => projectFilter(p.Project));
Would something like this using Join suit your needs?
Expression<Func<Company, bool>> masterExpression =
Company => Company.Participants.Join (Ids, p => p.Project.ID, id => id, (p, id) => p).Any();
IQueryable<Object> queryEntity = _db.Companies.Where(masterExpression);
I got this solution for avoiding execution of Lambda twice. To achieve this I used these extension methods Invoke() and AsExpandable(). Its available in Linqkit dll.
Expression<Func<Company, bool>> masterExpression = Company => Company.Participants.Any(part => masterLamba.Invoke(part.Project));
queryEntity = _db.Companies.AsExpandable().Where(masterExpression);

RIA DomainService IQueryable - Select

Following a couple different tutorials, I've been trying to build a "Silverlight Business Application" against a database I've created. I've found I have two problems. The one I'm asking about here is how to filter the query.
The query that is built in the DomainService is when using the VS2010 template is:
[EnableClientAccess]
public class ChargesService : LinqToEntitiesDomainService<ChargesEntities>
{
public IQueryable<tblChargeCode> GetCharges()
{
return ObjectContext.tblChargeCodes.OrderBy(e => e.Chrgs_Code_01).Take(10);
}
}
I'm trying to create another query against the same ObjectContext.tblChargeCodes. Pulling the entire table (30 columns by ~7k rows) creates a timeout error.
I can't figure out how to do a select. I want to select Charge_Codes_01 and Bill_Description with a "starts with" type functionality (dynamic drop down search functionality). I've tried different variations of this without success. Something just isn't clicking in my brain.
public IQueryable<tblChargeCode> SearchCharges(string num)
{
var min = System.Convert.ToInt32(num.PadRight(7, '0'));
var max = System.Convert.ToInt32(num.PadRight(7, '9'));
return ObjectContext.tblChargeCodes
.Select(e => e.Chrgs_Code_01, e.Chrgs_Code_01_Desc)
.Where(e => e.Chrgs_Code_01 >= min && e.Chrgs_Code_01 <= max)
.OrderBy(e => e.Chrgs_Code_01)
.Take(10);
}
(sorry for my bad english)
The problem with you code is the "Select" - your method signature says that it must return a IQueryable of tblChargeCode, so you cannot return a projection. Here is two ways you can write your query:
In the server:
public IQueryable<tblChargeCode> SearchCharges(int min, int max, string description)
{
return ObjectContext.tblChargeCodes
.Where(e => e.Chrgs_Code_01 >= min && e.Chrgs_Code_01 <= max)
.Where(e => e.Bill_Description.StartsWith(description))
.OrderBy(e => e.Chrgs_Code_01)
.Take(10);
}
And call it on the client:
context.Load(context.SearchChargesQuery(0, 9999999, "Bill"), (op) =>
{
//op.Entities has your entities loaded
}, null);
Or you can just leave the query on the server:
public IQueryable<tblChargeCode> GetCharges()
{
return ObjectContext.tblChargeCodes.OrderBy(e => e.Chrgs_Code_01);
}
And call it from the client (it will filter on the server)
context.Load(context.GetChargesQuery().Where(e => e.Chrgs_Code_01 >= 0 && e.Chrgs_Code_01 <= 9999999)
.Where(e => e.Bill_Description.StartsWith("Bill"))
.OrderBy(e => e.Chrgs_Code_01)
.Take(10), (op) =>
{
//op.Entities has your entities loaded
}, null);
You also can use "Contains" instead of "StartsWith" in your query.

Resources