C# Linq Contains statement - c#

I wrote my own object Tag and I would like to to contains if the .Value is found (I want to simulate WHERE IN like in SQL)
public static List<Question> GetQuestionsIdsWithTags(List<Tag> tags)
{
IEnumerable<Question> res = from t in dataClasses.tags
join
qt in dataClasses.question_to_tags on t.id equals qt.tag_id
join q in dataClasses.questions on qt.question_id equals q.id
where tags.Contains<Tag>(new Tag(t.name))
select new Question(q.text) { };
problem is, if the Contains is in the query, I get
The member 'Core.Literal.Value' has no supported translation to SQL.
Where Literal is the base of Tag.
What can I do?

You're trying to do new Tag(t.name), but this cannot be translated into SQL (the database server can't create new instances of your Tag class). Perhaps this would work:
IEnumerable<Question> res = from t in dataClasses.tags
join
qt in dataClasses.question_to_tags on t.id equals qt.tag_id
join q in dataClasses.questions on qt.question_id equals q.id
where tags.Select(x => x.name).Contains(t.name)
select new Question(q.text) { };

if tags is a List<string>, you should find that:
where tags.Contains(t.Name)
works fine; but there are limits to what it can understand (and more importantly, write as TSQL).

The LinQ Contains() statement can only be translated to SQL if it is performed by a List containing basic datatypes like int or string. If you need to cast your tags list to a List of strings or ints, then it should work.

Related

How do I group one to many in a nested fashion with linq to entities?

I have three tables, Chart, ChartQuestion, ChartAnswer
A Chart has many ChartQuestions, a ChartQuestion has many ChartAnswers.
I'd like to do a linq query that gives me an object containing all of this data, so it'd be a ChartObject containing List<ChartQuestion> and each ChartQuestion contains a List<ChartAnswer>
I started with this:
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions)
This seems to be the first step. However I want to include the ChartAnswers now, so I have to do another join to pull back the ChartAnswers but I don't know how to do this.
I can't do a join, and while I can do a from, I am not sure of the exact syntax.
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions
from chartQuestionsSelection in chartQuestions
join chartAnswer in context.ChartAnswers on chartQuestions.ChartAnswerId equals chartAnswer.ChartAnswerId into chartAnswers // This is wrong
)
With that code above you end up as chartAnswers being separate to the chartQuestions rather than belonging to them, so I don't think it is correct.
Any idea?
joining a table destructors it i.e. you now have the row as a variable if you need it in a nested fashion then select in like this
(from chart in db.Chart
select new { //can also be your own class/record, perhaps a DTO
chart.Id,
chart.Name,
questions = (from chartQuestion in db.chartQuestion
where chart.ChartId == chartQuestion.ChartId
select new { //perhaps a dto
chartQuestion.Id,
chartQuestion.Question,
Answers =
(from chartAnswer in context.ChartAnswers
where chartAnswer.ChartAnswerId == chartQuestion.ChartAnswerId
select chartAnswer).ToList() //this is translated and not evaluated
}).ToList() //this is translated not evaluated
}).ToListAsync(cancellationToken) //this will evaluate the expression
If you need it by joins then you can group join it like this:
from m in _context.Chart
join d in _context.ChartQuestions
on m.ID equals d.ID into mdJoin
select new
{
chartId = m.ID,
chartName = "m.name",
quess = from d in mdJoin
join dd in _context.ChartAnswer
on d.Iddomain equals dd.DomainId into anJoin
select new
{
quesId = d.ID,
quesName = d.Question,
anss = anJoin
}
}
A better way: If you edit your DbConfigurations to include navigation properties of each then the code will become way simpler.
example:
db.Chart
.Include(x => x.chartQuestions)
.ThenInclude(x => x.chartAnswers)
You can search more about how to do navigation properties in EFCore, but ofcourse if the code base is large then this might not be feasible for you.
Try following :
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId
join chartAnswer in context.ChartAnswers on chartAnswer.ChartAnswerId equals chartQuestion.ChartAnswerId
)

C# Linq Group by Object

I have an issue of using group by in LINQ to SQL statement.
The cod I have is
var combinedItems = (from article in articles
join author in authors
on article.AuthorId equals author.Id into tempAuthors
from tempAuthor in tempAuthors.DefaultIfEmpty()
select new { article , author = tempAuthor});
var groups1 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article into g
select g.Key).ToList();
var groups2 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article.Id into g
select g.Key).ToList();
I tried to group in two different ways. The first way, I group by an object and the second way I just group by a field in one of the objects.
When I run groups1, I got an error saying need to evaluate in client side, while when I use groups2, it works all good. Can I ask what could be wrong? If I want to group by object, is there any way to do it?
In case you want to group by object, as you've not overridden Equals and GetHashCode() in your Article class or implemented IEqualityComparer<Article> you're just getting the default comparison, which checks if the references are equal. So what you need is something like this:
class GroupItemComparer : IEqualityComparer<Article>
{
public bool Equals(Article x, Article y)
{
return x.Id == y.Id &&
x.Name == y.Name;
}
public int GetHashCode(Article obj)
{
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode();
}
}
And then you need to change your query to lambda expression:
var groups1 = combinedItems.GroupBy(c => c.article , new GroupItemComparer())
.Select(c => c.Key).ToList();
In case you got any exception regarding translation your method to SQL, you can use AsEnumerable or ToList methods before your GroupBy method, with this methods after data is loaded, any further operation is performed using Linq to Objects, on the data already in memory.
As others have pointed out, the GroupBy is using reference equality by default, and you could get around it by specifying one or more properties to group by. But why is that an error?
The whole point of the query is to translate your Linq query into SQL. Since object reference equality on the client can't be easily translated to SQL, the translator doesn't support it and gives you an error.
When you provide one or more properties to group by, the provider can translate that to SQL (e.g. GROUP BY article.Id), and thus the second method works without error.

Linq query for Where on the Joined table without needing join

Trying to get a linq query (or lambda syntax) for the following SQL which Selects all "Data" which in the joining table have an Attribute equal to "blob".
EXCEPT: without explictly using the Join, but the
select data.*
from data
join settings on data.DataID = settings.DataID
where settings.Attribute = 'blob'
Explicitly defining the join
from d in dbcontext.Data
join s in dbcontext.Settings on d.DataID equals s.DataID
where s.Attribute == "blob"
select d
but is there a way to use the context dbcontext.Data.Settings
like the following?
from d in dbcontext.Data
where d.Settings.Attribute == "blob"
select d
Settings is a collection Type, so things like .Contains, and .Where come to mind.
using .Contains, my understanding is i would need to pass in an object type
where d.Settings.Contains(new Settings(d.DataID, "blob", null))
but i dont care about the null (Value) matching, just column Settings
some table structures
Data
DataID
Name
Settings
DataID
Attribute
Value
As I understand, you have Settings collection navigation property, so instead of explicit join you could simply use it ("navigate"):
from d in dbcontext.Data
from s in d.Settings
where s.Attribute == "blob"
select d
Alternatively you could use Any extension method which in this case is more appropriate than Contains (although Contains can also be used, but needs to be combined with Select):
dbcontext.Data.Where(d => d.Settings.Any(s => s.Attribute == "blob"))
For completeness, here is the Contains version:
dbcontext.Data.Where(d => d.Settings.Select(s => s.Attribute).Contains("blob"))
If I understand your question correctly, you want to create a LINQ that will grab any DataID that has an attribute of of "Blah" that is stored in another table.
If so this may work.
var dataIDs = Setting.Where(entry => entry.Attribute == "Blah")
.Select(entry => entry.DataID); // gets all DataIDs that match the attribute
var data = Data.Where(entry => entry.DataID in dataIDs); // gets data info based on DataIDs.
It should work, but what you should do instead is do an left join somewhat like
select a.*
from data a
left join settings b
on a.DataID = b.DataID
where b.Attribute = 'blob'
but in LINQ. This query would allow you to fetch all the data for DataIDs that match attribute 'blob. I haven't done it in LINQ so if someone more familiar with left joins with linq could respond that might work better

Databinding to LINQ query result in Silverlight

I am retrieving simple LINQ query, but I am joining with two table and binding data with ListBox.
I am not able to properly show the item into the ListBox.
once I remove new item and select only keyword using it will work properly, but I want to join two table with select new key word it wil not allow to bind data with ListBox.
my code is like.
This will not allow to bind with ListBox.
var newPeople = (from p in clsGeneral.db.Table<SmartFXAttribes>()
join q in clsGeneral.db.Table<CategoryAttribes>() on p.catId equals q.ID
where p.catId == ((SmartFX.CategoryAttribes)((ComboBox)cmbPrintSize).SelectedValue).ID
select new
{
p.ID,
p.ImageHeight,
p.Imageoutline,
p.ImageUnit,
p.ImageWidth,
p.NoofPic,
p.TextboxCaption,
p.CanvasPixelHeight,
p.CanvasPixelWidth,
p.CanvasUnit,
p.catId,
q.FileName
}).ToList();
lstThumbnail.ItemsSource = newPeople;
This code will work fine.
var newPeople =
(from p in clsGeneral.db.Table<SmartFXAttribes>()
join q in clsGeneral.db.Table<CategoryAttribes>() on p.catId equals q.ID
where p.catId == ((SmartFX.CategoryAttribes)((ComboBox)cmbPrintSize).SelectedValue).ID
select p).ToList();
lstThumbnail.ItemsSource = newPeople;
Thanks!
The problem is that the first query creates an anonymous-typed object, but Silverlight cannot do data binding against an anonymous-typed object (anonymous types are internal and Silverlight's reflection capabilities do not allow accessing internal types from other assemblies). Your second query returns objects of a named type so it works just fine.
The best solution to this is to declare a public type containing public properties for everything you want to return from your first query and return an instance of that instead.
You can work around it with this hack, though.

Counting in a Linq Query

I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?

Categories