How to join two tables, and retrieve item collection using Linq - c#

I have two tables: baswareCatalog.FlowCurrent and baswareCatalog.Doc.
They both contain the attribute DocId.
The Doc-table also has the attributes LastExpireDate and Status.
FlowCurrent-table also has the attribute RecipientName.
I want to retrieve a collection of Doc, where the DocId of the item is the same in both tables.
Furthermore, this condition must be true: flowCurrent.RecipientName.Contains(userFullName)
I tried the following, but I don’t know if it is correct.
var docitemIQueryable = from flowCurrent in baswareCatalog.FlowCurrent join
document in baswareCatalog.Docs on
flowCurrent.DocId equals document.DocId
where flowCurrent.RecipientName.Contains(userFullName)
select new
{
Items = baswareCatalog
.Docs
.Where(b => b.DocId == flowCurrent.DocId)
};
The return-value is
'interface System.Linq.IQueryable<out T>'.
T is 'a'
Anonymous Types: 'a is new {IQueryable<Doc> Items }'
How do I retrieve a collection of Doc that I can iterate over?

Two things:
System.Linq.IQueryable is a collection that you can iterate over. You can put it in a foreach loop or do whatever you want with it. Or you can use .ToList() if you want to immediately materialize it.
You're doing extra work that you don't need to do in your query. You're doing a join between FlowCurrent and Docs, but then you're taking the results and going right back to Docs again. You don't need to do that.
Assuming what you care about is just the stuff in Docs, try this:
var docitemIQueryable = from flowCurrent in baswareCatalog.FlowCurrent
join document in baswareCatalog.Docs on
flowCurrent.DocId equals document.DocId
where flowCurrent.RecipientName.Contains(userFullName)
select document;
That will give you a collection of rows with all the fields in Docs. If you need some of the stuff in FlowCurrent as well then you'll need to do something more like this:
var docitemIQueryable = from flowCurrent in baswareCatalog.FlowCurrent
join document in baswareCatalog.Docs on
flowCurrent.DocId equals document.DocId
where flowCurrent.RecipientName.Contains(userFullName)
select new {
document.DocId, flowCurrent.blahblah, ...
};

Without more knowledge of your table structure, you're query looks ok. At the point of the 'select' you have the two tables joined and limited by your where clause.
Inside the select new, you can assign to the new object whatever properties from the original tables you want to include.
select new
{
DocId = flowCurrent.DocId,
LastExpireDate = document.LastExpireDate,
//etc
}
If you just want to get the doc items you can do this
var docitemIQueryable = from flowCurrent in baswareCatalog.FlowCurrent join
document in baswareCatalog.Docs on
flowCurrent.DocId equals document.DocId
where flowCurrent.RecipientName.Contains(userFullName)
select document;
and then you will have an iqueryable of documents

The result is a sequence of objects within items property so you need a nested loop or a SelectMany
foreach(var item in docitemIQueryable.SelectMany( x=> x.Items)){
//do something
}
However it seems your inner selection returns just one element in which case you can skip the anonymously type object and simply select like below
select document;
and simplify the looping to
foreach(var item in docitemIQueryable){
//do something
}

Related

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

LINQ Include Values of children error

I have this query:
var allValues = from p in _pContext.Persons
where p.Id == currentPerson.Id
from i in p.Items //This is a list that contains "Items"
select i;
I want to have all the Items and all the nested values that they contain. How do I load these when executing this query, too? I know theres the include statement that I can use on the context, but that doesnt lead anywhere. If I f.e. do this:
var allValues = from p in _pContext.Persons.Include("Items.Properties")
where p.Id == currentPerson.Id
from i in p.Items //This is a list that contains "Items"
select i;
to get all the items loaded with their associated "Properties", these properties arent loaded, their list is instanciated but it doesnt contains any.
Include has lots of delusive quirks. One of them is that an Include is ignored if the query shape changes after it is applied. This happens in your case. The Inlude works if the query looks like this:
from p in _pContext.Persons.Include("Items.Properties")
select p
This is because the path "Items.Properties" is traversable off of the entity in the end result: Person.
But now you change the shape of the query by changing the returned entity...
from p in _pContext.Persons.Include("Items.Properties")
from i in p.Items
select i
... and the include path is no longer valid. (Not traversable off of Item).
For Include there's a simple rule of the thumb: apply it at the end of the query. Doing that, you'll automatically enter the correct path, esp. when you use lambda syntax:
(from p in _pContext.Persons
from i in p.Items
select i).Include("Properties")
or
(from p in _pContext.Persons
from i in p.Items
select i).Include(i => i.Properties)

Dynamic linq query with join

Here is my try of dynamic Linq query with join. I try to get list of unique categories, brands and other criteria which were present in last reading in database. What is passed to the query (brand, category etc.) would only be defined at runtime.
I read about best way of doing this with func's, predicates etc., I think this is beyond my capacity at this stage. I'm trying the easier way with query string, which I got working for some simpler case, but I'm doing something wrong here with join. If I do just plain select product.Category with intellisense of course this works, but not in string in select clause.
public IEnumerable<string> getFilterItems(string dbColumn)
{
var filterItems = new List<string>();
return (from reading in Helper.LatestReadings
where reading.Distributor != Helper.Client
join product in Helper.Context.Products
on reading.ProductId equals product.SkuCode
select ("product." + dbColumn)).Distinct();
}
you can use reflection to achieve this
public IEnumerable<string> getFilterItems(string dbColumn)
{
var filterItems = new List<string>();
var productList = from reading in Helper.LatestReadings
where reading.Distributor != Helper.Client
join product in Helper.Context.Products
on reading.ProductId equals product.SkuCode
select product;
return productList.Select(x=>x.GetType().GetProperty(dbColumn).GetValue(x)).Cast<string>().Distinct();
}
"product." + dbColumn evaluates to a String, therefore select ("product." + dbColumn) will return this string n times, one time per item in the result of your query.
This SO question has an answer if you want to have a dynamic select. It proposes a method so that you could write something like
var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
EDIT:
Oh, sorry, I just read about "dynamic LINQ" here, therefore my above answer may not be that helpful. As I can see on the linked page, they use the following format in Select.
.Select("new(<ColumnName> as <DataMemberName>, ... )");
In your case you should try
.Select("new(product" + dbColumn + " as Value)");

Lambda or LINQ for Complex Filter?

I have a need to filter a large collection of objects (in memory) to select only those which meet ALL of the selected categories.
This is essentially the SQL query I'm looking to replicate, but I've been unable to come up with a good C# alternative:
select distinct o.ObjectId
from Object o
join ObjectCategories oc on oc.ObjectId = o.ObjectId
where oc.CategoryId in (1)
and oc.CategoryId in (2)
and oc.CategoryId in (3)
... and so on...
...where 1, 2, and 3 represent the values in an indeterminate number of user-selected categories.
Have your user selected category ID's in a List and then you can use Contains.
select distinct o.ObjectId
from Object o
join ObjectCategories oc on oc.ObjectId = o.ObjectId
where yourCategoryList.Contains(oc=>oc.categoryID);
var results = ObjectCategories.Where(t2 => ObjectList.Any(t1 => t2.Contains(t1)) == true)
you can count the number of matches and if it is equal to the list you are checking against, then you have all the matches.
Consider using Dynamic LINQ. It allows you to use string expressions instead of lambda expressions. You should be able to do what you want using something similar to:
var qry = ObjectCategories.Where(
"ObjectList.CategoryId.Contains(1) AND ObjectList.CategoryId.Contains(2) ..."
);
There is a pretty solid implemention of Dynamic LINQ here: https://github.com/NArnott/System.Linq.Dynamic

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.

Categories