LINQ 2 SQL - using a C# entity collection and SqlMethods.Like together - c#

I have a collection of headers, each which contains a collection of objects, each of which contains a collection of metadata key value pairs. eg
> Header
> -> Object
> -> Key value pair
> -> Key value pair
> -> Object
> -> Key value pair
> -> Key value pair
I want to return all headers, which contain an object, which contains a certain key value pair in the metadata, using SQL wildcards (using SqlMethods.Like).
I have written a LINQ 2 SQL query below with two levels of subqueries which handles the scenario
string filePath = "ab%cd";
var dbHeaders = from h in _repository.GetHeaders()
where
(from o in h.Objects
where
(from mdp in o.MetaDataPairs
where mdp.Key == Constants.FilePath && SqlMethods.Like(mdp.Value.ToLower(), filePath))
select mdp
).Any()
select o).Any()
select h;
This works fine.
The problem arises when I have a list of possible search values to search on. Ie I want to find objects containing a metadata value from a list of possible matches, not just a single match. I tried the below.
var filePaths = new List<string> { "ab%cd", "ef%gh" };
var dbHeaders = from h in _repository.GetHeaders()
where
(from o in h.Objects
where
(from mdp in o.MetaDataPairs
where mdp.Key == Constants.FilePath && filePaths.Any(fp => SqlMethods.Like(mdp.Value.ToLower(), fp))
select mdp
).Any()
select o).Any()
select h;
but because SQLMethods.Like is contained within Filepaths.Any() it doesn't work. as it has to occur natively in the LINQ 2 SQL query.
How can I modify the top query to match, using SQL Like operator, so that it checks against a list of string search tokens, not a single one?
UPDATE: Error message below
Assert.IsFalse failed. An unexpected error occurred: LINQ to Entities
does not recognize the method 'Boolean Like(System.String,
System.String)' method, and this method cannot be translated into a
store expression.

The issue is, as you've mentioned, that once you put the SqlMethods.Like in the filePaths.Any it throws an exception.
That's because SqlMethods.Like is not supported in linq-to-entity, and filePath is an entity.
You need to, somehow, dynamically create multiple OR statements with SqlMethods.Like.
I've found a very similar question and an interesting answer that should help:
How can I add variable count of SqlMethods.Like() in one query?
The solution takes advantage of PredicateBuilder from C# 6.0 in a Nutshell.
I believe you could adapt your code into something like the following:
I didn't test the code, ofcourse.
var filePaths = new List<string> { "ab%cd", "ef%gh" };
var likeExpression = PredicateBuilder.False<MetaDataPairClassName>();
foreach (string filePath in filePaths)
{
likeExpression = likeExpression.Or(mdp =>
SqlMethods.Like(mdp.Value.ToLower(), filePath));
}
var dbHeaders = from h in _repository.GetHeaders()
where
(from o in h.Objects
where
(from mdp in o.MetaDataPairs
.Where(mdp.Key == Constants.FilePath)
.Where(likeExpression))
select mdp
).Any()
select o).Any()
select h;

Related

SQL Selection to Array in C#

I am querying in C# for the first time, so please forgive my ignorance. I want to query a table, then place the results in an array/dict/dataframe to then be accessed later. I am unable to run the final code on my end, so this is more of an exercise in setting up the queries for when the final code (a chatbot) works.
Here is the code that should work to get boiling points and melting points seperately. Assume that casnumber is declared in advance (let's just call it str '753')
boiling_point = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.boiling_point).FirstOrDefault();
melting_point = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.metling_point).FirstOrDefault();
How would I get the results of the query to an array/dict/dataframe instead?
dict = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.boiling_point,
cdls.melting_point).FirstOrDefault();
Ideally, I would want {(boiling_point : 200F), (melting_point : 100F)} as output, or something similar in a table/df/array. There are 30+ attributes in the table, so a way to assign key-value pairs or create a dataframe from the query for each attribute queried would be ideal.
Get a list of Tuples like this
var tuples = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select (cdls.boiling_point, cdls.melting_point))
.ToList();
tuples will be a list of tuples (ex. List<(string boiling_point, string melting_point)>)
for (var tuple in tuples)
{
var boiling_point = tuple.boiling_point;
var melting_point= tuple.melting_point;
}

LINQ to Entities group by and Count()

I have the following LINQ-to-Entities query
from r in ctx.Rs
join p in ctx.Ps on r.RK equals p.RK
group r by r.QK into gr
select new { QK = (int)gr.Key, Num = gr.Count() }
that runs against this schema
Table P Table R Table Q
PK*
RK ----> RK*
Text QK ------> QK*
Text Text
and gives this message if there is any record in Q with no corresponding record in P: "The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type."
The problem is the gr.Count() in the last line, but I cannot find a solution. I have tried to test gr for null, but cannot find a way that works.
I have seen a number of solutions to a similar problem using Sum() instead of Count(), but I have not been able to adapt them to my problem.
I tried changing my query to look like the one in Group and Count in Linq issue, but I just got a different message.
I also looked at Group and Count in Entity Framework (and a number of others) but the problem is different.
group Key can't be null
var results = ctx.Rs.Where(r => r.QK != null)
.GroupBy(r => r.QK)
.Select(gr => new { Key = (int)gr.Key, Count = gr.Count() }
.ToList();
PS.
Mostly, You don't need 'JOIN' syntax in Entity Framework. see: Loading Related Entities
Writing descriptive-meaningful variable names would significantly improve Your codes and make it understandable. Readability does matter in real world production.
I'm having trouble reading your format. But can you try:
from r in ctx.Rs
join p in ctx.Ps.DefaultIfEmpty() on r.RK equals p.RK
group r by r.QK into gr
select new { QK = (int)gr.Key, Num = gr.Count(x => x.RK != null) }
With DefaultIfEmpty and x => x.RK != null being the changes.

Linq to SQL - Query

I am trying to mimic below statement in Linq to SQL.
WHERE (rtrim(posid) like '%101' or rtrim(posid) like '%532')
I statement basically determine if posid ends with 101 or 532. In the above example I am only making 2 comparisons but their could be 1 to N comparisons all joined with OR. I store the comparison values (101,532,...) in a generic list that I send to my Linq to SQL method.
I have tried to mimic above SQL using a where clause unsuccessfully (example below):
var PosNum = new List<string>();
PosNum.Add("101");
PosNum.Add("532");
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => PosNum.Contains(p.posid.Trim()));
The issue with the above where clause is that it tries to do an exact match rather I want an ends with comparison.
How would I mimic the SQL statement in Linq to SQL.
Thank You in advance for any help / advice you can provide.
I would use String.EndsWith();
This will check the end of the string rather than entire contents of it.
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => p.posid.EndsWith("102") || p.posid.EndsWith("532"));
In EF 4 you can use the StartsWith / EndsWith methods by now. Might also work in LINQ to SQL.
UPDATE
Just realized that you are trying todo this against multiple values (PosNum), I don't think that this is directly supported currently. You can however concatenate multiple Where()clauses to get the result.
UPDATE 2
As AdamKing pointed out concatenating the where clauses was filtering against all PosNum values, here is the corrected version:
var baseQuery = (from a in context.tbl_sspos select a);
IEnumerable<YourType> q = null;
foreach(var pos in PosNum)
{
if(q == null)
q = baseQuery.Where(a => a.posid.EndsWith(pos));
else
q = q.Union(baseQuery.Where(a => a.posid.EndsWith(pos)));
}
This is not as pretty anymore, but works nonetheless.

Linq Union: How to add a literal value to the query?

I need to add a literal value to a query. My attempt
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
In the above example, I get an error:
"Local sequence cannot be used in LINQ to SQL implementation
of query operators except the Contains() operator."
If I am using Entity Framework 4 for example, what could I add to the Union statement to always include the "seed" ID?
I am trying to produce SQL code like the following:
select distinct ID
from product
union
select 0 as ID
So later I can join the list to itself so I can find all values where the next highest value is not present (finding the lowest available ID in the set).
Edit: Original Linq Query to find lowest available ID
var skuQuery = Context.Products
.Where(p => p.sku > skuSeedStart &&
p.sku < skuSeedEnd)
.Select(p => p.sku).Distinct();
var lowestSkuAvailableList =
(from p1 in skuQuery
from p2 in skuQuery.Where(a => a == p1 + 1).DefaultIfEmpty()
where p2 == 0 // zero is default for long where it would be null
select p1).ToList();
var Answer = (lowestSkuAvailableList.Count == 0
? skuSeedStart :
lowestSkuAvailableList.Min()) + 1;
This code creates two SKU sets offset by one, then selects the SKU where the next highest doesn't exist. Afterward, it selects the minimum of that (lowest SKU where next highest is available).
For this to work, the seed must be in the set joined together.
Your problem is that your query is being turned entirely into a LINQ-to-SQL query, when what you need is a LINQ-to-SQL query with local manipulation on top of it.
The solution is to tell the compiler that you want to use LINQ-to-Objects after processing the query (in other words, change the extension method resolution to look at IEnumerable<T>, not IQueryable<T>). The easiest way to do this is to tack AsEnumerable() onto the end of your query, like so:
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().AsEnumerable().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
Up front: not answering exactly the question you asked, but solving your problem in a different way.
How about this:
var a = Products.Select(p => p.sku).Distinct().ToList();
a.Add(0);
a.Dump(); // LinqPad's way of showing the values
You should create database table for storing constant values and pass query from this table to Union operator.
For example, let's imagine table "Defaults" with fields "Name" and "Value" with only one record ("SKU", 0).
Then you can rewrite your expression like this:
var zero = context.Defaults.Where(_=>_.Name == "SKU").Select(_=>_.Value);
var result = context.Products.Select(p => p.sku).Distinct().Union(zero).ToList();

Linq query with multiple where's [duplicate]

This question already has answers here:
Multiple WHERE clause in Linq
(3 answers)
Closed 2 years ago.
I am trying the to query my Status Update repository using the following
var result = (from s in _dataContext.StatusUpdates
where s.Username == "friend1" && s.Username == "friend2" etc...
select s).ToList();
Instead of using s.Username == "friendN" continuously is there anyway I can pass a list or array or something like that rather that specifying each one, or can I use a foreach loop in the middle of the query.
Thanks
If you only need to check whether the Username property has some specified value, you can create a list of the values and then use method such as All or Any to check if some condition holds for any/all elements of the array.
Your example looks a bit suspicious though - the user name s.Username cannot be equal to multiple different strings. Did you want to check whether it is equal to any of the (specified) names? That could be written like this:
var friends = new[] { "friend1", "friend2", ... };
var result =
from s in dc.StatusUpdates
where friends.Any(fr => s.Username == fr)
select s;
This returns all status updates such that the Username property is equal to any of the specified friend names (specified as an array, but you could use any IEnumerable<string>).
Yo could do it like this:
IQueryable<s> query= _dataContext.StatusUpdates;
foreach (var item in names)
{
query = query.Where(p=>p.Username == item);
}
List<s> result = query.ToList();
I think I mucked with some data types of yours but this should be close:
var names = new List<string>();
// populate names
var updates = new List<StatusUpdate>();
// populate updates
var result = (from s in updates
where names.Contains(s.ToString())
select s).ToList();

Categories