How to combine linq queries and selecting distinct records? - linq

I have an object that can have a single user assigned to it or a work group. A user may be assigned directly or though a work group, but the object can never have both set.
public class Procedure
{
.....
public Guid? AssignedToId {get;set;} //Foreign Key to AssignedTo
public Contact AssignedTo {get;set;} //Single user assignment
public Guid? AssignedWorkGroupId {get;set;} //Foreign Key to AssignedWorkGroup
public WorkGroup AssignedWorkGroup {get;set;} //Multiple user assignment
public Guid? AssignedBuisnessPartnerId {get;set;}
public BusinessPartner AssignedBuisnessPartner {get;set;}
}
I am trying to figure out how to write a single query where I can find procedures where a user may be assigned directly or is part of a work group that is assigned. Currently I have 2 separate queries and combining the lists I get back. Which works, but probably not as efficient.
Here is what I have now:
var procedures = _procedureRepository.Get(p => p.AssignedToId == assignedId).ToList();
procedures.AddRange(_procedureRepository.Get(p => p.AssignedWorkGroup.Contacts.Select(c => c.Id).Contains(assignedId) || p.AssignedBuisnessPartner.Contacts.Select(c => c.Id).Contains(assignedId));

It looks like you are looking for a Union All in sql, which is equivalent to Concat in linq. The following code will only execute one call to the database. Not sure if it will be faster than your current method.
var procedures2 = _procedureRepository.Get(p => p.AssignedWorkGroup.Contacts
.Select(c => c.Id)
.Contains(assignedId) ||
p.AssignedBuisnessPartner.Contacts
.Select(c => c.Id)
.Contains(assignedId));
var procedures = _procedureRepository.Get(p => p.AssignedToId == assignedId)
.Concat(procedures2);

Related

Returning a list inside Linq query from left outer join

I'm new with Linq and hoping for some clarity on a particular query.
I have two tables (simplified for demonstration):
Table: Customer
CustomerId | Name
1 | John Smith
2 | Peter James
Table: Order
id | CustomerId | Total
1 | 1 | $100
2 | 1 | $200
Sample CustomerDto:
public class CustomerDto
{
public long CustomerId { get; set; }
public string Name{ get; set; }
public CustomerOrder[] CustomerOrderList{ get;set;}
}
Linq example for left outer join in the select here, they return string.empty if the join fails, I'm not sure the equivalent when I'm returning an object rather than a string.
My Linq query looks as follows. I've used the DefaultIfEmpty() to assist in a left outer join, however given I'm dealing with my object, I'm not sure how to return null if there isn't anything.
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale
};
As I mentioned, I want to return a list of orders rather than one row per order. So there should be two records returned (the two customers), one with a list of orders and the other without any.
How do I achieve this?
The first step to make the entities easier to work with is to ensure that navigation properties are set up. If the table is called "Order" then the entity can be Order, or renamed to CustomerOrder if you like. A Customer entity can have an ICollection<Order> collection which EF can automatically map provided a consistent naming convention and normalization is used. (Otherwise explicit mapping can be provided)
For example:
public class Customer
{
[Key]
public int CustomerId { get; set; }
// other customer fields.
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
From here you are projecting Customers to a CustomerDTO, so we should also project the Orders to an OrderDTO. Note that when using navigation properties we don't have to explicitly join entities. We don't even have to eager load related data via Include(). The later would apply if/when we want to work with the entities rather than projections.
The resulting query would end up looking like:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders.Select(o => new OrderDto
{
OrderId = o.OrderId,
Total = o.Total
}).ToList()
});
The benefit is no need to explicitly write Join expressions. EF can help simplify accessing related data considerably rather than just facilitating using Linq as an alternative to SQL. This would return an empty list rather than #null if there are no Orders for that customer. It may be possible to substitute a #null if there aren't any orders, though worst case it could be post-processed after the results are materialized Ie:
var customers = await search.ToListAsync();
var noOrderCustomers = customers.Where(c => !c.Orders.Any()).ToList();
foreach(var customer in noOrderCustomers)
customer.Orders = null;
It really just boils down to whether the consumer is Ok knowing there is always an Orders collection that will be empty if there are no orders, or in the Orders collection is only present if there are orders. (via JSON etc. serialization)
The important details to consider: When filtering, such as filling in search criteria, do this before the Select as the IQueryable is working with entities so you have full access to the table fields. Adding Where clauses after the Select will limit the available fields to the ones you have selected for the DTO. (They will still bubble down into the SQL) There is a ToList inside the Select to build the Orders collection. This may look "bad" that it might be materializing data synchronously, but it will be executed only when the main query is. (Such as an awaited async operation on the IQueryable)
When projecting to DTOs be sure not to mix DTOs and entities such as:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders
});
... which can be tempting. The issue here is that "c.Orders" would return a collection of Order entities. Those Orders may have references to other entities or information you don't need to/want to expose to the consumer. Accessing references could result in lazy load costs, null references, or exceptions (i.e. disposed DbContext) depending on when/where they occur.
just put the ternary condition to achieve it like follows:
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale == null ? null : subCustomerSale // add this line and you will get the null as well if there is no record
};

using NEST with Elastic Search for collections

I'm trying to get my hands dirty with Elastic Search via the NEST .Net api and running into a couple of problems. I suspect I've misunderstood something, or am modelling my docs incorrectly but would appreciate some help.
I have a document with collections in it. A similar trite example below :
public class Company
{
public DateTime RegisteredOn {get;set;}
public string Name {get;set;}
[ElasticProperty(Type = FieldType.nested)]
public List<Employee> Employees {get;set;}
}
public class Employee
{
public string FirstName {get;set;}
public string LastName {get;set;}
[ElasticProperty(Type = FieldType.nested)]
public List<SalesFigure> SalesFigures {get;set}
}
public class SaleFigure
{
public int AverageMonthlySaleValue {get;set;}
public int AverageVolumeSold {get;set;}
}
I've created an index with some data in at each level of the hierarchy and before indexing have called client.MapFromAttributes<Company>();
The following works, but I'd like to understand how I'd find all companies with employees with a firstName of Bob, and or find all companies with employees who have a an average AverageMonthlySaleValue > $1100
client.Search<Company>(query => query.Index("companies").Type("company")
.From(0)
.Size(100)
.Filter(x => x.Term(n => n.Name, "Microsoft")));
Nested queries/filters have been suggested as has suggestions that I ought to flatten my document which I can do, but I'm trying to create a model which better represents the real domain so am in a quandary.
Equally, I know that I'll also have to use facets at some point so want to structure everything correctly to support that.
Thanks
Tim
So it turns out there wasn't much wrong with the structure of my document. The example is trite and the real property I was querying on a collection was a string, not an int, so case sensitivity kicked in.
I had to change the query to use a lower case string value for comparison which worked. Something like the following worked.
client.Search<Company>(query => query.Index("companies")
.Type("company")
.From(0)
.Size(100)
.Filter(x => x.Term("company.employees.firstName", "microsoft")));
I've still to work out how to use a lamda in place of "company.employees.firstName" but it works for now.

LINQ, how to specify to include items from List<>

Ok, I am trying to find get all subcategories from my database (using query result shaping) that belong to the category that I supply. My class SubCategory includes a List<> of Categories.
The problem is that in the linq statement, g is referring to SubCategory (which in the end contains Categories<>). So the statement below is not allowed.
How do I change the Linq statement to generate the correct SQL query to include all SubCategories that contain the matching Category.
public class SubCategory
{
public int SubCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Article> Articles { get; set; }
public List<Category> Categories { get; set; }
}
//incorrect code below:
var SubCategories = storeDB.SubCategories.Include("Categories").Single(g => g.Name == category);
This worked for me (maybe too simple):
var Category = storeDB.Categories.Include("SubCategories").Single(c => c.Name == category);
return Category.SubCategories;
I find it a bit confusing that each SubCategory can belong to more than one Category - have you got this relationship the right way around?
Regardless, I think its probably more readable if you change the select to work on Categories first - i.e. something like:
var subCatQuery = from cat in storeDB.Categories
where cat.Name == category
select cat.SubCategories;
which you can then execute to get your IEnumerable<>:
var subCategories = subCatQuery.ToList();
I find that much more readable/understandable.
(I also find the query syntax easier to read here than the fluent style)
my preferred answer would be to use a linq join. if there aren't but 2 data sets you can use the linq operations .join and .groupjoin vs a from statement. this would be my approach.
Dictionary<MainQuery, List<subQuery>> Query =
dbStore.List<MainQuery>.include("IncludeAllObjects")
.groupjoin(db.List<SubTable>.Include("moreSubQueryTables"),
mainQueryA=>mainQueryA.PropToJoinOn,
subQueryB => sunQueryB.PropToJoinOn,
((main, sub)=> new {main, sub})
.ToDictionary(x=>x.main, x=>x.sub.ToList());

Dynamic Linq Search Expression on Navigation Properties

We are building dynamic search expressions using the Dynamic Linq library. We have run into an issue with how to construct a lamba expression using the dynamic linq library for navigation properties that have a one to many relationship.
We have the following that we are using with a contains statement-
Person.Names.Select(FamilyName).FirstOrDefault()
It works but there are two problems.
It of course only selects the FirstOrDefault() name. We want it to use all the names for each person.
If there are no names for a person the Select throws an exception.
It is not that difficult with a regular query because we can do two from statements, but the lambda expression is more challenging.
Any recommendations would be appreciated.
EDIT-
Additional code information...a non dynamic linq expression would look something like this.
var results = persons.Where(p => p.Names.Select(n => n.FamilyName).FirstOrDefault().Contains("Smith")).ToList();
and the class looks like the following-
public class Person
{
public bool IsActive { get; set;}
public virtual ICollection<Name> Names {get; set;}
}
public class Name
{
public string GivenName { get; set; }
public string FamilyName { get; set; }
public virtual Person Person { get; set;}
}
We hashed it out and made it, but it was quite challenging. Below are the various methods on how we progressed to the final result. Now we just have to rethink how our SearchExpression class is built...but that is another story.
1. Equivalent Query Syntax
var results = from person in persons
from name in person.names
where name.FamilyName.Contains("Smith")
select person;
2. Equivalent Lambda Syntax
var results = persons.SelectMany(person => person.Names)
.Where(name => name.FamilyName.Contains("Smith"))
.Select(personName => personName.Person);
3. Equivalent Lambda Syntax with Dynamic Linq
var results = persons.AsQueryable().SelectMany("Names")
.Where("FamilyName.Contains(#0)", "Smith")
.Select("Person");
Notes - You will have to add a Contains method to the Dynamic Linq library.
EDIT - Alternatively use just a select...much more simple...but it require the Contains method addition as noted above.
var results = persons.AsQueryable().Where("Names.Select(FamilyName)
.Contains(#0", "Smith)
We originally tried this, but ran into the dreaded 'No applicable aggregate method Contains exists.' error. I a round about way we resolved the problem when trying to get the SelectMany working...therefore just went back to the Select method.

LINQ self referencing query

I have the following SQL query:
select
p1.[id],
p1.[useraccountid],
p1.[subject],
p1.[message],
p1.[views],
p1.[parentid],
max(
case
when p2.[created] is null then p1.[created]
else p2.[created]
end
) as LastUpdate
from forumposts p1
left join
(
select
id, parentid, created
from
forumposts
) p2 on p2.parentid = p1.id
where
p1.[parentid] is null
group by
p1.[id],
p1.[useraccountid],
p1.[subject],
p1.[message],
p1.[views],
p1.[parentid]
order by LastUpdate desc
Using the following class:
public class ForumPost : PersistedObject
{
public int Views { get; set; }
public string Message { get; set; }
public string Subject { get; set; }
public ForumPost Parent { get; set; }
public UserAccount UserAccount { get; set; }
public IList<ForumPost> Replies { get; set; }
}
How would I replicate such a query in LINQ? I've tried several variations, but I seem unable to get the correct join syntax. Is this simply a case of a query that is too complicated for LINQ? Can it be done using nested queries some how?
The purpose of the query is to find the most recently updated posts i.e. replying to a post would bump it to the top of the list. Replies are defined by the ParentID column, which is self-referencing.
The syntaxt of left join in LINQ is :
(i put it in VB.NET) :
Dim query = From table1 in myTable.AsEnumarable 'Can be a collection of your object
Group join table2 in MyOtherTable.AsEnumerable
On table1.Field(Of Type)("myfield") Equals table2.Field(Of Type)("myfield")
In temp
From table2 in temp.DefaultIsEmpty()
Where table1.Field(Of Type)("Myanotherfield") is Nothing 'exemple
Select New With { .firstField = table1.Field(Of Type)("Myanotherfield")
.secondField = table2.Field(Of Type)("Myanotherfield2")}
Something like that
Ju
I discovered that NHibernate LINQ support doesn't include joins. That, coupled with an apparent inexperience with complex LINQ queries, I resorted to the following work around:
Add a Modified column to the posts table.
On reply, update parent's Modified column to match reply's Created column
Sort by and retrieve the value of the Modified column for post display.
I think it's a pretty clean work around, given the limitations of the code. I dreadfully wanted to avoid having to resort to adding another entity, referencing a view, or using a stored procedure + data table combination for this particular piece of code only. Wanted to keep everything within the entities and use NHibernate only, and this fix allows that to happen with minimal code smell.
Leaving this here to mark as answer later.

Resources