LINQ translates when not part of a property - c#

I have a query like this:
var q = db.GetTable<Person>().Where(x => x.Employer.CEO != null);
This made up query will return the ID of the CEO of the company a given person works for. This works find and dandy, but if I do something like this, it get the failed to translate to SQL error:
public class Person
{
public bool HasCEO
{
get
{
return this.Employer.CEO != null;
}
}
}
I want to be able to do this and wrap the longer expression within a property so that I don't have to repeat a nested table get:
var q = db.GetTable<Person>().Where(x => x.HasCEO);
How do I create LINQ properties to achieve my desired result?
I'm using C# 4.0 if that matters.

You can't. LINQ to SQL works by examining the expression tree of the query, and does not delve into the implementation of properties to determine whether a row meets your criteria.
What you can do is create a view in SQL server that dynamically calculates this property, and query that instead of the table.

Related

It is possible to query a NotMapped property?

i'm working with EF6 code first, and i used this answer to map a List<stirng> in my entitie.
This is my class
[Key]
public string SubRubro { get; set; }
[Column]
private string SubrubrosAbarcados
{
get
{
return ListaEspecifica == null || !ListaEspecifica.Any() ? null : JsonConvert.SerializeObject(ListaEspecifica);
}
set
{
if (string.IsNullOrWhiteSpace(value))
ListaEspecifica.Clear();
else
ListaEspecifica = JsonConvert.DeserializeObject<List<string>>(value);
}
}
[NotMapped]
public List<string> ListaEspecifica { get; set; } = new List<string>();
It works perfectly to storage my list as Json, but now i need to perform a linq query, and i'm trying this
var c = db.CategoriaAccesorios.Where(c => c.ListaEspecifica.Contains("Buc")).First();
And it's throwing
System.NotSupportedException: The specified type member
'ListaEspecifica' is not supported in LINQ to Entities. Only
initializers, entity members, and entity navigation properties are
supported.
what is logical.
Is there any way to perform a query like this?
The problem here is that LINQ to Entities does not understand how to convert your query to the back-end (SQL) language. Because you're not materializing (i.e. converting to .NET) the results of the query until you filter it, LINQ tries to convert your query to SQL itself. Since it's not sure how to do that, you get a NotSupportedException.
If you materialize the query first (I.e. call a .ToList()) then filter, things will work fine. I suspect this isn't what you want, though. (I.e. db.CategoriaAccesorios.ToList().Where(c => c.ListaEspecifica.Contains("Buc")).First();)
As this answer explains, your issue is the EF to SQL Conversion. Obviously you want some way to workaround it, though.
Because you are JSON serializing, there are actually a couple options here, most particularly using a LIKE:
var c =
(from category
in db.CategoriaAccessorios
where SqlMethods.Like(c.SubrubrosAbarcados, "%\"Buc\"%")
select category).First()
If EF Core, allegedly Microsoft.EntityFrameworkCore.EF.Functions.Like should replace SqlMethods.Like.
If you have SQL Server 2016+, and force the SubrubrosAbarcados to be a JSON type, it should be possible to use a raw query to directly query the JSON column in particular.
If you're curious about said aspect, here's a sample of what it could look like in SQL Server 2016:
CREATE TABLE Test (JsonData NVARCHAR(MAX))
INSERT INTO Test (JsonData) VALUES ('["Test"]'), ('["Something"]')
SELECT * FROM Test CROSS APPLY OPENJSON(JsonData, '$') WITH (Value VARCHAR(100) '$') AS n WHERE n.Value = 'Test'
DROP TABLE Test
I was able to do something like this via CompiledExpression.
using Microsoft.Linq.Translations;
// (...) namespace, class, etc
private static readonly CompiledExpression<MyClass, List<string>> _myExpression = DefaultTranslationOf<MyClass>
.Property(x => x.MyProperty)
.Is(x => new List<string>());
[NotMapped]
public List<string> MyProperty
{
get { return _myExpression.Evaluate(this); }
}
I hope there are better / prettier solutions though ;)

How to dynamically build and store complex linq queries against a list of hierarchical objects?

I have a list of objects in a hierarchical structure. I want to build complex LINQ queries against that object list based on "conditions" a client company sets and are stored in the database. So I need to build these at run time, but because they will be run repeatedly whenever the client's users update or refresh their data I would like to store the LINQ queries in objects rather than rebuild them each time.
I have looked at ScottGu's Blog about Dynamic LINQ.
Also this article about using expression trees.
Neither of these appear to provide an adequate solution, but I may not be understanding them adequately. I'm afraid that I'm trying to use LINQ when I should consider other options.
My object hierarchy:
WorkOrder[]
Field[]
Task[]
Field[]
Here is an example of a LINQ query that I would like to store and execute. I can reasonably build this format based on the database records that define the conditions.
var query =
from wo in WorkOrders
from woF in wo.Fields
from task in wo.Tasks
from taskF in task.Fields
from taskF2 in task.Fields
where woF.Name == "System Status"
&& woF.Value.Contains("SETC")
&& taskF.Name == "Material"
&& taskF.Value == "Y"
&& taskF2.Name == "Planner"
&& taskF2.Value == "GR5259"
select new
{
wo_id = wo.ID,
task_id = task.ID
};
A few considerations.
Depending on the complexity of the user defined conditions I may or may not need to pull from the different object lists: the "froms" are dynamic.
Note that in this example I pulled twice from the task.fields[] so I aliased it two times.
The example LINQ structure allows me to have complex ANDs, ORs, parenthesis, etc. that I don't believe is practical with Dynamic Chaining or Expression Trees.
In my code I envision:
//1) Retrieve business rules from DB. I can do this.
//2) Iterate through the business rules to build the linq queries.
foreach (BusinessRule br in BusinessRules) {
//Grab the criteria for the rule from the DB.
//Create a linq to object query based on the criteria just built.
//Add this query to a list for later use.
}
...Elsewhere in application.
//Iterate through and execute the linq queries in order to apply business rules to data cached in the application.
foreach (LinqQuery q in LinqQueries) {
//Execute the query
//Apply business rule to the results.
}
Thank you very much for your thoughts, effort and ideas.
You can technically achieve what you need using only LINQ, but the PredicateBuilder is a nice utility class:
public enum AndOr
{
And,
Or
}
public enum QueryableObjects
{
WorkOrderField,
TaskField
}
public class ClientCondition
{
public AndOr AndOr;
public QueryableObjects QueryableObject;
public string PropertyName;
public string PropertyValue;
}
public void PredicateBuilderExample()
{
var conditions = new List<ClientCondition> {
{
new ClientCondition { AndOr = LINQ.AndOr.And,
QueryableObject = QueryableObjects.WorkOrderField,
PropertyName = "System Status",
PropertyValue = "SETC"
}
},
{
new ClientCondition{AndOr = AndOr.And,
QueryableObject = QueryableObjects.TaskField,
PropertyName = "Material",
PropertyValue = "Y"
}
},
{
new ClientCondition{AndOr = AndOr.Or,
QueryableObject = QueryableObjects.TaskField,
PropertyName = "Planner",
PropertyValue = "GR5259"
}
}
};
//Obviously this WorkOrder object is empty so it will always return empty lists when queried.
//Populate this yourself.
var WorkOrders = new List<WorkOrder>();
var wofPredicateBuilder = PredicateBuilder.True<WorkOrderField>();
var tfPredicateBuilder = PredicateBuilder.True<TaskField>();
foreach (var condition in conditions)
{
if (condition.AndOr == AndOr.And)
{
if (condition.QueryableObject == QueryableObjects.WorkOrderField)
{
wofPredicateBuilder = wofPredicateBuilder.And(
wof => wof.Name == condition.PropertyName &&
wof.Value.Contains(condition.PropertyValue));
}
}
if (condition.AndOr == AndOr.Or)
{
if (condition.QueryableObject == QueryableObjects.TaskField)
{
tfPredicateBuilder = tfPredicateBuilder.Or(
tf => tf.Name = condition.PropertyName &&
tf.Value.Contains(condition.PropertyValue));
}
}
//And so on for each condition type.
}
var query = from wo in WorkOrders
from woF in wo.Fields.AsQueryable().Where(wofPredicateBuilder)
from task in wo.Tasks
from taskF in task.Fields.AsQueryable().Where(tfPredicateBuilder)
select new
{
wo_id = wo.ID,
task_id = task.ID
};
}
Note that I use the enums to limit the possible conditions your clients can send you. To have a truly dynamic queryable engine, you will need to use Reflection to ensure the object names you receive are valid. That seems like a rather large scope, and at that point I would recommend researching a different approach, such as ElasticSearch.
Also note that the order of And and Ors matters significantly. Essentially you are allowing your customers to build SQL queries against your data, and that usually ends in tears. It's your job to limit them to the proper set of conditions they should be querying.
Based on the discussion with Guillaume I would only suggest to pay attention to the type of the resulting query when playing around with advanced dynamic query generation. If you are changing the shape of what is being returned via Select, Aggregate, or one of the other methods you will expect your inner type to change accordingly. If you are just filtering with Where you can keep adding on as many additional cases you want unless you want OR behavior then things like PredicateBuilder helps. If you want to pull in more data via Join, Zip, ... then you are either doing so to filter, add to the rows returned, and possibly change the shape of the data.
I've done a lot of this in the past and had most success focusing on specific helper methods that allow for common cases that I need and then leaning on linq expression trees and patterns such as the visitor pattern to allow custom expression built at runtime.

Using variables to build a LinQ query?

I don't think is possible but wanted to ask to make sure. I am currently debugging some software someone else wrote and its a bit unfinished.
One part of the software is a search function which searches by different fields in the database and the person who wrote the software wrote a great big case statement with 21 cases in it 1 for each field the user may want to search by.
Is it possible to reduce this down using a case statement within the Linq or a variable I can set with a case statement before the Linq statement?
Example of 1 of the Linq queries: (Only the Where is changing in each query)
var list = (from data in dc.MemberDetails
where data.JoinDate.ToString() == searchField
select new
{
data.MemberID,
data.FirstName,
data.Surname,
data.Street,
data.City,
data.County,
data.Postcode,
data.MembershipCategory,
data.Paid,
data.ToPay
}
).ToList();
Update / Edit:
This is what comes before the case statement:
string searchField = txt1stSearchTerm.Text;
string searchColumn = cmbFirstColumn.Text;
switch (cmbFirstColumn.SelectedIndex + 1)
{
The cases are then done by the index of the combo box which holds the list of field names.
Given that where takes a predicate, you can pass any method or function which takes MemberDetail as a parameter and returns a boolean, then migrate the switch statement inside.
private bool IsMatch(MemberDetail detail)
{
// The comparison goes here.
}
var list = (from data in dc.MemberDetails
where data => this.IsMatch(data)
select new
{
data.MemberID,
data.FirstName,
data.Surname,
data.Street,
data.City,
data.County,
data.Postcode,
data.MembershipCategory,
data.Paid,
data.ToPay
}
).ToList();
Note that:
You may look for a more object-oriented way to do the comparison, rather than using a huge switch block.
An anonymous type with ten properties that you use in your select is kinda weird. Can't you return an instance of MemberDetail? Or an instance of its base class?
How are the different where statements handled, are they mutually excluside or do they all limit the query somehow?
Here is how you can have one or more filters for a same query and materialized after all filters have been applied.
var query = (from data in dc.MemberDetails
select ....);
if (!String.IsNullOrEmpty(searchField))
query = query.Where(pr => pr.JoinDate.ToString() == searchField);
if (!String.IsNullOrEmpty(otherField))
query = query.Where(....);
return query.ToList();

LINQ to SQL where clause verifying string contains list element

I'm using a view returning Domains according to an id. The Domains column can be 'Geography' or can be stuffed domains 'Geography,History'. (In any way, the data returned is a VARCHAR)
In my C# code, I have a list containing main domains:
private static List<string> _mainDomains = new List<string>()
{
"Geography",
"Mathematics",
"English"
};
I want to filter my LINQ query in order to return only data related to one or many main Domain:
expression = i => _mainDomains.Any(s => i.Domains.Contains(s));
var results = (from v_lq in context.my_view
select v_lq).Where(expression)
The problem is I can't use the Any key word, nor the Exists keyword, since they aren't available in SQL. I've seen many solutions using the Contains keyword, but it doesn't fit to my problem.
What should I do?
You can use contains:
where i.Domains.Any(s => _mainDomains.Contains<string>(s.xxx))
Notice that the generic arguments are required (even if Resharper might tell you they are not). They are required to select Enumerable.Contains, not List.Contains. The latter one is not translatable (which I consider an error in the L2S product design).
(I might not have gotten the query exactly right for your data model. Please just adapt it to your needs).
I figured it out. Since I can't use the Any keyword, I used this function:
public static bool ContainsAny(this string databaseString, List<string> stringList)
{
if (databaseString == null)
{
return false;
}
foreach (string s in stringList)
{
if (databaseString.Contains(s))
{
return true;
}
}
return false;
}
So then I can use this expression in my Where clause:
expression = i => i.Domains.ContainsAny(_mainDomains);
Update:
According to usr, the query would return all the values and execute the where clause server side. A better solution would be to use a different approach (and not use stuffed/comma-separated values)

How to share one LINQ query between two methods?

is there any way to share one LINQ query between two methods? I have quite long LINQ query that gets search results from database and I need to use this query to get results (some kind of list<>) -first method - and to get its count (int) - second method -. I don't want to copy this query in two separate methods and I can't return custom class object containing search results and records count (returned by this query). So what I want to do is to get LINQ query definition(or something like this?) but no the results set that I can use in other methods. Maybe there is another good way to do that. thanks for yout help ;)
the code look like this:
public ??? GetSearchResultsQuery(SearchRequest search_request)
{
var queryGetSearchResults = ....
return queryGetSearchResults;
}
public int GetSearchResultsCount(SearchRequest search_request)
{
return GetSearchResultsQuery(search_request).Count();
}
public List<SearchResults> GetSearchResults(SearchRequest search_request)
{
return GetSearchResultsQuery(search_request).Skip(search_request.startRowIndex).Take(search_request.maximumRows).ToList();
}
public IQueryable<SearchedForType> GetSearchResultsQuery(SearchRequest search_request)
{
var queryGetSearchResults = context.SearchedForTypes.Where(x => x == search_request.X);
... build up your search query ...
return queryGetSearchResults;
}

Categories