Querying 2 Sets of Complex-Objects Using Linq - c#

I have two lists comprised of different complex-objects, and each one is from 2 separate data-sources. One list may-or-may-not contain records. When any records exist in the "optional" list I need the "normal" list to be further-filtered.
Unfortunately, I can only find very simple examples here and online, which is why I am asking this question.
The Pseudo-Logic Goes Like This:
When QuickFindMaterial records exist, get all DataSource records where query.Name is in the QuickFindMaterial.Material collection. If no QuickFindMaterial records exist do not affect the final result. Lastly, select all distinct DataSourcerecords.
The Classes Looks Like:
public class QuickFindMaterial
{
public string SiteId { get; set; }
public string Material { get; set; }
}
The Code Looks Like:
I have commented-out my failed WHERE logic below
var dataSource = DocumentCollectionService.ListQuickFind();
var quickFindMaterial = ListMaterialBySiteID(customerSiteId);
var distinct = (from query in dataSource
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
})
//.Where(x => x.Name.Contains(quickFindMaterial.SelectMany(q => q.Material)))
//.Where(x => quickFindMaterial.Contains(x.Name))
.Distinct();

I think this is what you want:
.Where(x => !quickFindMaterial.Any() || quickFindMaterial.Any(y => x.Name == y.Material))

You could join on Name -> Material
Example:
var distinct = (from query in dataSource
join foo in quickFindMaterial on query.Name equals foo.Material
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
}).Distinct();

Related

LINQ aggregate multiple tables

I have the following database. A list of companies. Each company has multiple employees and multiple contractors.
dbo.Companies (CompanyId, Name)
dbo.Employees (Id, CompanyId, Name ...)
dbo.Contractors(Id, CompanyId, Name...)
I want to get output like so
CompanyName #Employees #Contractors
abc 0 10
xyz 25 999
I am trying to avoid doing 2 queries, one to get contractors and one to get employees and then merging them. Is there a way to get it done in one go?
n.b. i have
class CompanySummary{
string Name {get; set;}
int EmpCount {get; set;}
int ConCount {get; set;}
}
so I can use a collection of this type as result
If you have defined navigation properties (and if you haven't, may be it's a good time to do that), the query should be quite simple:
var query = from c in db.Companies
select new CompanySummary
{
Name = c.Name,
EmpCount = c.Employees.Count(),
ConCount = c.Contractors.Count(),
};
Of course you can do that manually, but the above is the preferred way with EF:
var query = from c in db.Companies
select new CompanySummary
{
Name = c.Name,
EmpCount = db.Employees.Count(e => e.CompanyId == c.Id),
ConCount = db.Contractors.Count(cc => cc.CompanyId == c.Id),
};
In both cases you'll get a single SQL query.
If you are using Entity Framework to communicate with the database and have the tables linked with foreign keys you can probably do it in one query. It would look something like this:
IEnumerable<CompanySummary> companySummary = null;
using (CompanyEntities dbContext = new CompanyEntities())
{
companySummary = dbContext.Companies
.Include(company => company.Employees)
.Include(company => company.Contractors)
.Select(company => new CompanySummary
{
Name = company.Name,
EmpCount = company.Employees.Count(),
ConCount = company.Contractors.Count()
});
}

how to use Linq to get a sitecore field

I have a search that I'm wanting to exclude any search result that has the name of the "Title" field in the search. For example, say I type in "Contact" in the search bar. I don't want the Contact Us page to come up, but if someone wants to search something that has words in the Contact Us page, then its ok. I am able to get templateName and IDs but can't seem to get fields...
Item homeItem = Main.Utilities.SitecoreUtils.getHomeItem();
var query = PredicateBuilder.True<SearchResultItem>();
query = query.And(i => i.Paths.Contains(homeItem.ID));
query = query.And(i => i.Content.Contains(searchTerm));
query = query.And(i => i.TemplateName != "MenuFolder");
This is what I have, but I want to add something to it to exclude the "Title" field and maybe the "SEO" field. So probably something like:
query = query.And(i => i.Fields["Title"];
But in this case its including not excluding it. And I can't do:
query = query.And(i != i.Fields["Title"];
It won't accept that answer.
Try to use code like that i => !i["Title"].Contains(searchTerm):
Item homeItem = Main.Utilities.SitecoreUtils.getHomeItem();
var query = PredicateBuilder.True<SearchResultItem>();
query = query.And(i => i.Paths.Contains(homeItem.ID));
query = query.And(i => i.Content.Contains(searchTerm));
query = query.And(i => i.TemplateName != "MenuFolder");
query = query.And(i => !i["Title"].Contains(searchTerm));
If you want to strongly type it, you just need to extend the SearchResultItem class with your field name.
public class SitecoreItem : SearchResultItem
{
[IndexField("title")]
public string Title { get; set; }
[IndexField("__smallcreateddate")]
public DateTime PublishDate { get; set; }
[IndexField("has_presentation")]
public bool HasPresentation { get; set; }
}
Then your code would be like this
IQueryable<SitecoreItem> query = context.GetQueryable<SitecoreItem>();
SearchResults<SitecoreItem> results = null;
query = query.Where(x => x.Title.Contains(searchText);
results = query.GetResults();

Update property in object collection with linq

Have a list of objects with the object structure as following
public class Schedule
{
public int ID { get; set; }
public string Name { get; set; }
public Schedule() {}
}
Executing a linq query on data I can see properly populated list of objects
var schedule = (from d in data
select new Schedule
{
ID = d.id,
Name = ""
}).ToList();
later in code I want to change property Name depends on a condition. A few examples I found
schedule.ForEach(s => { s.Name = "Condition Name"; });
schedule.Select(s => { s.Name = "Condition name"; return s; });
after execution leave Name parameter "null" when I refresh schedule in the watch window.
Can anyone see what's wrong with this?
Looping through collection and trying to change Property doesn't change it either
foreach (var sch in schedule)
{
sch.Name = "New name";
}
schedule.ToList()[0].Name == ""
UPDATE
.ToList() call in the snippet below is important to make code work.
var schedule = (from d in data
select new Schedule
{
ID = d.id,
Name = ""
}).ToList();
Your LINQ query that assigns a value to schedule creates independent objects based on the original collection (it effectively clones the objects); changing the properties of the clones does not change the original objects.
The code works - after executing schedule.ForEach() the Name property is updated. Maybe there is something that you've left out.
LINQ is not the right tool to modify collections, it is a tool to query collections. If you want to modify it you need a loop, for example a foreach:
var schedule = data.Select(d => new Schedule{ ID = d.id }).ToList();
foreach(var s in schedule)
s.Name = "Condition Name";
If you want to "modify" a collection with LINQ you have to create a new one and assign that to your variable which is inefficient.
Below, you are create new Schedules without setting its Name.
var schedule = (from d in data
select new Schedule
{
ID = d.id
}).ToList();
To start, you may want to give the new Schedules a Name
var schedule = (from d in data
select new Schedule
{
ID = d.id,
Name = d.NameProperty /* Switch out with real name property */
}).ToList();

Combining Tables With Different Data Using Linq in MVC?

I have Two classes Named OfflineOrderLineItem.cs and OnlineOrderLineItem.cs both have diff Order table named offline and Online
In that i want to Combine the two tables data to search and Display the Fields from both tables
How to do that using linq in mvc4 ??? any idea.....
public virtual IPagedList<OnlineOrderLineItem> SearchOrderLineItems(string PoNumber)
{
var query1 = (from ol in _offlineOrderLineItemRepository.Table
select new
{
ol.Name
}).ToList();
var query2 = (from opv in _onlineOrderLineItemRepository.Table
select new
{
opv.Name
}).ToList();
var finalquery = query1.Union(query2);
if (!String.IsNullOrWhiteSpace(Name))
finalquery = finalquery.Where(c => c.Name == Name);
var orderlineitems = finalquery.ToList(); //its not working it throw a error
return new PagedList<OnlineOrderLineItem>(orderlineitems);//error
}
Error
cannot convert from 'System.Collections.Generic.List<AnonymousType#1>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'
query1 and query2 are lists of an anonymous type with a single property of type string. (I assmume the ol.Name and opv.Name are strings.) Hence finalQuery and orderlineitems are collections of this anonymous as well. By specifying PagedList<T> you require that the collection passed into the constructor is an enumeration of type T. T is OnlineOrderLineItem, but the enumeration passed into the constructor is the anonymous type which is a different type. Result: compiler error.
To solve the problem I suggest that you define a named helper type that you can use to union the two different types OfflineOrderLineItem and OnlineOrderLineItem:
public class OrderLineItemViewModel
{
public int Id { get; set; }
public string PoNumber { get; set; }
public string Name { get; set; }
// maybe more common properties of `OfflineOrderLineItem`
// and `OnlineOrderLineItem`
}
Then your SearchOrderLineItems method should return a paged list of that helper type:
public virtual IPagedList<OrderLineItemViewModel> SearchOrderLineItems(
string PoNumber)
{
var query1 = from ol in _offlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = ol.Id,
PoNumber = ol.PoNumber,
Name = ol.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var query2 = from opv in _onlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = opv.Id,
PoNumber = opv.PoNumber,
Name = opv.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var finalquery = query1.Union(query2);
// again no ToList here
if (!string.IsNullOrWhiteSpace(PoNumber))
finalquery = finalquery.Where(c => c.PoNumber == PoNumber);
var orderlineitems = finalquery.ToList(); // DB query runs here
return new PagedList<OrderLineItemViewModel>(orderlineitems);
}
It is important to use ToList only at the very end of the query. Otherwise you would load the whole tables of all OnlineOrderLineItems and all OfflineOrderLineItems into memory and then filter out the items with the given PoNumber in memory which would be a big overhead and performance desaster.
Instead of
var orderlineitems = finalquery.ToList();
Try
var orderlineitems = finalquery.AsQueryable();
From https://github.com/TroyGoode/PagedList/blob/master/src/PagedList/PagedList.cs, PagedList takes a IQueryable<T>
Queryable.AsQueryable<TElement> Method

Join/Where with LINQ and Lambda

I'm having trouble with a query written in LINQ and Lambda. So far, I'm getting a lot of errors here's my code:
int id = 1;
var query = database.Posts.Join(database.Post_Metas,
post => database.Posts.Where(x => x.ID == id),
meta => database.Post_Metas.Where(x => x.Post_ID == id),
(post, meta) => new { Post = post, Meta = meta });
I'm not sure if this query is correct.
I find that if you're familiar with SQL syntax, using the LINQ query syntax is much clearer, more natural, and makes it easier to spot errors:
var id = 1;
var query =
from post in database.Posts
join meta in database.Post_Metas on post.ID equals meta.Post_ID
where post.ID == id
select new { Post = post, Meta = meta };
If you're really stuck on using lambdas though, your syntax is quite a bit off. Here's the same query, using the LINQ extension methods:
var id = 1;
var query = database.Posts // your starting point - table in the "from" statement
.Join(database.Post_Metas, // the source table of the inner join
post => post.ID, // Select the primary key (the first part of the "on" clause in an sql "join" statement)
meta => meta.Post_ID, // Select the foreign key (the second part of the "on" clause)
(post, meta) => new { Post = post, Meta = meta }) // selection
.Where(postAndMeta => postAndMeta.Post.ID == id); // where statement
You could go two ways with this. Using LINQPad (invaluable if you're new to LINQ) and a dummy database, I built the following queries:
Posts.Join(
Post_metas,
post => post.Post_id,
meta => meta.Post_id,
(post, meta) => new { Post = post, Meta = meta }
)
or
from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }
In this particular case, I think the LINQ syntax is cleaner (I change between the two depending upon which is easiest to read).
The thing I'd like to point out though is that if you have appropriate foreign keys in your database, (between post and post_meta) then you probably don't need an explicit join unless you're trying to load a large number of records. Your example seems to indicate that you are trying to load a single post and its metadata. Assuming that there are many post_meta records for each post, then you could do the following:
var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();
If you want to avoid the n+1 problem, then you can explicitly tell LINQ to SQL to load all of the related items in one go (although this may be an advanced topic for when you're more familiar with L2S). The example below says "when you load a Post, also load all of its records associated with it via the foreign key represented by the 'Post_metas' property":
var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);
var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;
var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically
It is possible to make many LoadWith calls on a single set of DataLoadOptions for the same type, or many different types. If you do this lots though, you might just want to consider caching.
Daniel has a good explanation of the syntax relationships, but I put this document together for my team in order to make it a little simpler for them to understand. Hope this helps someone
Your key selectors are incorrect. They should take an object of the type of the table in question and return the key to use in the join. I think you mean this:
var query = database.Posts.Join(database.Post_Metas,
post => post.ID,
meta => meta.Post_ID,
(post, meta) => new { Post = post, Meta = meta });
You can apply the where clause afterwards, not as part of the key selector.
Posting because when I started LINQ + EntityFramework, I stared at these examples for a day.
If you are using EntityFramework, and you have a navigation property named Meta on your Post model object set up, this is dirt easy. If you're using entity and don't have that navigation property, what are you waiting for?
database
.Posts
.Where(post => post.ID == id)
.Select(post => new { post, post.Meta });
If you're doing code first, you'd set up the property thusly:
class Post {
[Key]
public int ID {get; set}
public int MetaID { get; set; }
public virtual Meta Meta {get; set;}
}
I've done something like this;
var certificationClass = _db.INDIVIDUALLICENSEs
.Join(_db.INDLICENSECLAsses,
IL => IL.LICENSE_CLASS,
ILC => ILC.NAME,
(IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
.Where(o =>
o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
.Select(t => new
{
value = t.PSP_INDLICENSECLAsse.ID,
name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,
})
.OrderBy(x => x.name);
It could be something like
var myvar = from a in context.MyEntity
join b in context.MyEntity2 on a.key equals b.key
select new { prop1 = a.prop1, prop2= b.prop1};
This linq query Should work for you. It will get all the posts that have post meta.
var query = database.Posts.Join(database.Post_Metas,
post => post.postId, // Primary Key
meta => meat.postId, // Foreign Key
(post, meta) => new { Post = post, Meta = meta });
Equivalent SQL Query
Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Query Syntax for LINQ Join
var productOrderQuery = from product in Product.Setup()//outer sequence
join order in OrderDetails.Setup()//inner sequence
on product.Id equals order.ProductId //key selector
select new//result selector
{
OrderId = order.Id,
ProductId = product.Id,
PurchaseDate = order.PurchaseDate,
ProductName = product.Name,
ProductPrice = product.Price
};
Method Syntax for LINQ Join
var productOrderMethod = Product.Setup().//outer sequence
Join(OrderDetails.Setup(), //inner sequence
product => product.Id//key selector
,order=> order.ProductId //key selector
,(product,order)=> //projection result
new
{
OrderId = order.Id,
ProductId = product.Id,
PurchaseDate = order.PurchaseDate,
ProductName = product.Name,
ProductPrice = product.Price
}
);
Product.cs for reference
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public static IEnumerable<Product> Setup()
{
return new List<Product>()
{
new Product(){Id=1, Name="Bike", Price=30.33M },
new Product(){Id=2, Name="Car", Price=50.33M },
new Product(){Id=3, Name="Bus", Price=60.33M }
};
}
}
OrderDetails.cs class for reference
class OrderDetails
{
public int Id { get; set; }
public virtual int ProductId { get; set; }
public DateTime PurchaseDate { get; set; }
public static IEnumerable<OrderDetails> Setup()
{
return new List<OrderDetails>()
{
new OrderDetails(){Id=1, ProductId=1, PurchaseDate= DateTime.Now },
new OrderDetails(){Id=2, ProductId=1, PurchaseDate=DateTime.Now.AddDays(-1) },
new OrderDetails(){Id=3, ProductId=2, PurchaseDate=DateTime.Now.AddDays(-2) }
};
}
}
1 equals 1 two different table join
var query = from post in database.Posts
join meta in database.Post_Metas on 1 equals 1
where post.ID == id
select new { Post = post, Meta = meta };

Categories