dynamic filter with linq2sql - entity-framework

I am working on an asp.net core 2.0 mvc web application.
I have an sql-server database.
I need to build a complex filter. Please note i have simplified the example bellow.
Func<PersonEntity, bool> expr1 = (x => x.email.Contains("value1");
Func<PersonEntity, bool> expr2 = (x => x.email.Contains("value2");
Func< PersonEntity, bool> filter1 = (x => expr1(x) || expr2(x));
bdd.persons.Where(x => x.id<100).Where(filter1);
The SQL query which is received by sql server is:
SELECT * FROM Persons WHERE id<100
I suppose that my dynamic filter filter1 is executed by C# code.
What i want is to execute all filters in sql query. How can i do this ?
Thanks

Related

D365 CE: difference between OrganizationServiceContext and QueryExpression through Service?

I'm trying to execute a LINQ query within a plugin, using the OrganizationServiceContext, to retrieve some quotes. On these quotes, I'm using .Select() to only select the value for the field cgk_totalnetprice, as shown below:
quotes = OrganizationServiceContext.QuoteSet
.Where(_ =>
_.OpportunityId != null &&
_.OpportunityId.Id == opportunityId &&
_.QuoteId != currentQuote.Id &&
(_.StatusCode.Value == (int)Quote_StatusCode.Won || _.StatusCode.Value == (int)Quote_StatusCode.WonOrder) &&
_.cgk_quotetypecode != null &&
(_.cgk_quotetypecode.Value == (int)QuoteTypeCode.Regular || _.cgk_quotetypecode.Value == (int)QuoteTypeCode.ServiceUnderWarranty))
.Select(x => new Quote() { Id = x.Id, cgk_totalnetprice = x.cgk_totalnetprice})
.ToList();
However, when retrieving those quotes, the context does not return a value for all except one quote (and it is not the quote that triggered the update in the first place, but just a random one that was not updated at all)
Weird part: when I rewrite the query to a QueryExpression, everything works perfectly:
QueryExpression qe = new QueryExpression("quote");
//Exclude current quote
qe.Criteria.AddCondition("quoteid", ConditionOperator.NotEqual, currentQuote.Id);
//Opportunity
qe.Criteria.AddCondition("opportunityid", ConditionOperator.NotNull);
qe.Criteria.AddCondition("opportunityid", ConditionOperator.Equal, opportunityId);
//State-Status
FilterExpression statusFilter = new FilterExpression(LogicalOperator.Or);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.Won);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.WonOrder);
qe.Criteria.AddFilter(statusFilter);
//QuoteType
qe.Criteria.AddCondition("cgk_quotetypecode", ConditionOperator.NotNull);
FilterExpression typeFilter = new FilterExpression(LogicalOperator.Or);
typeFilter.AddCondition("cgk_quotetypecode", ConditionOperator.Equal, (int)QuoteTypeCode.Regular);
typeFilter.AddCondition("cgk_quotetypecode", ConditionOperator.Equal, (int)QuoteTypeCode.ServiceUnderWarranty);
qe.Criteria.AddFilter(typeFilter);
qe.ColumnSet = new ColumnSet("quoteid", "cgk_totalnetprice");
quotes = this.OrganizationService.RetrieveMultiple(qe).Entities.Cast<Quote>().ToList();
What could cause this difference between OrganizationServiceContext and OrganizationService + QueryExpression??
Queries on OrganizationServiceContext rely on LINQ for CRM, which in turn translates LINQ expressions into QueryExpression objects. LINQ for CRM comes with a few weaknesses:
it does not implement all capabilities of the underlying QueryExpression,
it only supports a limited set of LINQ constructs (see MS Docs),
in some cases it creates incorrect queries,
query processing is approx. 10% slower.
Your query looks pretty straightforward, yet it fails. Maybe you can leave the line _.cgk_quotetypecode != null && out. I guess it is not needed and combined with the subsequent filtering on the same attribute it may trick the LINQ parser into constructing the wrong filter and/or conditions.
Another option is to materialize the LINQ query first and then select the columns needed. Of course this will lead to a select *, but it's often worth trying while troubleshooting.
E.g. you could write:
.ToArray()
.Select(x => new Quote() { Id = x.Id, cgk_totalnetprice = x.cgk_totalnetprice});
Working with Dynamics CRM/365 CE I learned to avoid LINQ for CRM. Instead I use a bunch of extension methods allowing me to create QueryExpression queries in a much less verbose way.
Final suggestion: in some cases a filter's LogicalOperator.Or can be replaced by ConditionOperator.In or ConditionOperator.Between. Doing so the construct
//State-Status
FilterExpression statusFilter = new FilterExpression(LogicalOperator.Or);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.Won);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.WonOrder);
qe.Criteria.AddFilter(statusFilter);
can simply be replaced by this oneliner:
qe.Criteria.AddCondition("statuscode", ConditionOperator.In, (int)Quote_StatusCode.Won, (int)Quote_StatusCode.WonOrder);
I generally use the query syntax for Dynamics LINQ queries. I'd suggest standard query troubleshooting - start with no conditions and add them one-by-one.
Below is an idea of how my query would look. I'd keep adding / modifying the where clauses until the query returned what I expected.
We could use && operators instead of multiple where clauses, but I find that having the multiple where clauses often makes commenting and uncommenting easier.
using(var ctx = new OrganizationServiceContext(ctx))
{
var x = from q in ctx.CreateQuery<Quote>()
where q.QuoteId ! = currentQuote.Id
where q.OpportunityId != null
where q.cgk_quotetypecode != null
where q.cgk_quotetypecode == QuoteTypeCode.Regular || QuoteTypeCode.ServiceUnderWarranty
select new Quote
{
Id = q.Id,
cgk_totalnetprice = x.cgk_totalnetprice
};
var quotes = x.ToList();
}

How to sort on DB side, if entities are not connected via Navigation (since it's not possible)

I want to have my EfCore query translated into the following SQL query:
select
c.blablabla
from
codes c
left join lookups l on c.codeId = l.entityid and l.languageCode = <variable - language code of current thread> and l.lookuptype = 'CODE'
where
..something..
order by
l.displayname
Note: tables 'codes' and 'lookups' are not connected! 'lookups' contains a lot of different lookup data in different languages!
I am stuck into limitations of EfCore (like 'NavigationExpandingExpressionVisitor' failed). I don't want to make in-memory filtering, it looks silly to me... Am I missing something obvious?
In perspective, I'd like to make universal method to help sort by displayname (or other lookup name) for different kind of entities - not only codes.
Seems like I figured it out. If there's a better approach - please let me know:
protected override IQueryable<FixCode> SortByDisplayName(IQueryable<FixCode> queryable, string languageCode = null)
{
return queryable
.GroupJoin(
DbContext.FixCodeValues.Where(x =>
x.DomainId == CentralToolConsts.Domains.CENTRAL_TOOLS
&& x.CodeName == CentralToolsFieldTypes.CODE_ORIGIN
&& (x.LanguageCode == languageCode || x.LanguageCode == CentralToolsDbLanguageCodes.English)),
//TODO: this will be a 'selector' parameter
code => code.CodeOriginId,
codeOrigin => codeOrigin.StringValue,
(c, co) => new
{
Code = c,
CodeOrigin = co
}
)
.SelectMany(
x => x.CodeOrigin.DefaultIfEmpty(),
(x, codeOrigin) => new { Code = x.Code, CodeOrigin = codeOrigin }
)
.OrderBy(x => x.CodeOrigin.ShortName)
.Select(x => x.Code);
}

Is it possible to combine multiple IQueryable into a single one without using Union?

Good evening. Is it possible to combine multiple IQueryable into a single one without using Union in EF Core 2? The main problem is that it doesn't allow to use methods like .Union(), .Except() etc.
It's important that IQueryable should be executed as a single query.
This is what I want to get: toCreateSubquery.Union(toDeleteSubquery)
The queries I want to combine are listed below.
var toCreateSubquery =
validLinks
.Where(
rl => !existingLinks.Any(
el => el.Contract == rl.Contract && el.Estate == rl.Estate)) // Except() doesn't work'
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Create});
var toDeleteSubquery =
existingLinks
.Where(el => !validLinks.Any(rl => el.Contract == rl.Contract && el.Estate == rl.Estate))
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Delete});
This is the visualization of the problem I'm solving.
I want to get the union of these sets without intersection and be able to distinguish belonging to one of these sets.
Just in case, I attach the code of getting these sets:
var validLinks = from ac in _context.AreaContracts
from e in _context.Estate
where (from al in e.AreaLinks select al.Area.Id).Contains(ac.Area.Id) ||
e.Geometry.Intersects(
ac.Area.Geometry)
select new { Contract = ac.Contract.Id, Estate = e.Id };
var existingLinks = from sd in _context.SquareDistributions
select new { Contract = sd.Contract.Id, Estate = sd.Estate.Id };
Thank you for your attention.

Dynamic linq query in EF

I am using dynamic LINQ query in EF4.
Below code throws error: 'New' cannot be resolved into a valid type or function.
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy("it.CM_CODE")
.Select("New(it.CM_CODE, it.CM_NAME)");
What am I doing wrong?
The below code executes without any error.
var x = from cmp in ent.OM_COMPANY
where (qry)
orderby cmp.CM_CODE
select new { cmp.CM_CODE, cmp.CM_NAME };
I don't even know how you got that first code block to compile. Both OrderBy and Select take lambdas not strings. It should be written as:
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy(c => c.CM_CODE)
.Select(c => c.CM_CODE, c.CM_NAME);

In Linq to Entities can you convert an IQueryable into a string of SQL?

eg.
var result = myObject.Where(x => x.prop == 5);
string s = result.toSQL();
Result:
s is "SELECT * FROM [myObjects] WHERE prop = 5"
If it's IQueryable/ObjectQuery you can use ToTraceString. If it's IDbSet/DbSet you can use ToString directly.
Using EF 6 I could not get .ToString() to return SQL for something similar to:
db.Entry(parent)
.Collection(p => p.Children)
.Query()
.Where(c => c.Active)
.Load();
Then I remembered you can log it to debug output with:
db.Database.Log = (entry) => System.Diagnostics.Debug.WriteLine(entry);
Just set above before loading queries (duh) :)
db here is an instance of some DbContext derived class.