What does this LINQ do? - linq

I am trying to learn LINQ query expressions. In MS website, I found this LINQ query.
public void Linq16()
{
List<Customer> customers = GetCustomerList();
var orders =
from c in customers
from o in c.Orders
where o.OrderDate >= new DateTime(1998, 1, 1)
select new { c.CustomerID, o.OrderID, o.OrderDate };
ObjectDumper.Write(orders);
}
I am not sure what that c.Orders does. I visualized customers and orders like data tables. Can someone tell me what this c.Orders does and what is it called?

It means that the entity Customer has a related entity, namely Order. The table might look like:
Order
OrderID
OrderData
CustomerID (this is a Foreign Key to the Customer table)
Customer
CustomerID
Name
...
The Entity Framework will recognize the relationship between the two tables and create classes, similar to following:
public class Customer
{
public int CustomerID { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderID { get; set; }
public DateTime OrderDate { get; set;}
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
To understand the code a little more, the double from could be rewritten as:
foreach(Customer c in customers)
{
foreach(Order o in c.Orders)
{
if(o.OrderDate >= new DateTime(1998, 1, 1))
{
yield return new { c.CustomerID, o.OrderID, o.OrderDate };
}
}
}

This is NOT LINQ, but LINQ to entities.
Its a related entity. Basically its another table, and you can use it through including it.
When doing it with sql, you would have to select the table by the foreign key, EF does that automatically for you.
What you're doing there is the same as Include: MSDN blog example.
Include() is the Method syntax of the part of the query you dont understand.
Entity Framework creates a list of items of the other table as member of your entity - which is weird in the first place (just unusual actually) but very very useful, since you can ToList() the entity and they will be included if you .Include() them before.
To be very exact, you have a List (or something similar to list) added to the customer object (this is done by entity framework) and every Order is a line in the Orders table.
If you do want to use linq, but not Entity Framework, ignore it and search for other sources to learn linq.
Here is a really good resource to learn: MSDN, 101 LINQ examples

Related

EF6 Linq Query - How to make a query when two Foreign Key Constraints are in place

For the most part EF seems to handle itself quite well, using the following query in linq, I am able to get all the related table data using FK's without having to specify the one to many relationship.
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
select new ClinicalASSPATVM
{
ClinicalAssetID = s.ClinicalAssetID,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
InspectionDocumnets = subpat.InspectionDocumnets ?? String.Empty,
InspectionOutcomeResult = subpat.InspectionOutcomeResult
});
the above code pulls in the relationship data from the ViewModel.
public Product ProductName { get; set; }
public InspectionOutcome InspectionOutcomeResult { get; set; }
public Model ModelName { get; set; }
public BudgetCode Code { get; set; }
public AssetType AssetTypeName { get; set; }
public Manufacturer ManufacturerName { get; set; }
public Staff StaffName { get; set; }
public Team TeamName { get; set; }
public Supplier SupplierName { get; set; }
I have a new problem which I have created for myself. I wanted to add an ID Field to the Models entity this helps me filter the data in a drop down list. I called the Field Name: ModelAssetAsignmentID And when someone adds a new ModelName from the Clinical Controller the ModelAssetAsignmentID gets a value of two.
So when i added the Field ModelAssetAsignmentID to the model "Models" i created a second FK as such:
My Original Linq Query is now broken, it no longer displays the modelname. I'm guessing this is due to the two FK Constraints.
Making the following change did not work, the InnerException is null.
var ClinicalASSPATVM = (from s in db.ClinicalAssets
where (s.ModelAssetAssignmentID.Equals(2))
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
The solution is to remove the second foreign key constraint ModelAssetAssignmentID and use a viewmodel to create a value in the ModelAssetAssignmentID, thus you then do not need to modify the linq query's.

How can I manually join cached Entity Framework objects?

I'm having a performance issue with lookups using the navigation properties of an EF model.
My model is something like this (conceptually):
public class Company
{
public int ID { get; set; }
public string CompanyName { get; set; }
public EntityCollection<Employee> Employees { get; set; }
}
public class Employee
{
public int CompanyID { get; set; }
public string EmployeeName { get; set; }
public EntityReference<Company> CompanyReference { get; set; }
}
Now let's say I want to get a list of all Companies that have (known) Employees.
Additionally, assume that I've already cached lists of the both the Companies and the Employees through previous calls:
var dbContext = new EmploymentContext();
var allCompanies = dbContext.Companies.ToList();
var allEmployees = dbContext.Employees.ToList();
bool activeCompanies =
allCompanies.Where(company => company.Employees.Any()).ToList();
This (in my environment) generates a new SQL statement for each .Any() call, following the Employees navigation property.
I already have all the records I need in my cached lists, but they're not 'connected' to each other on the client side.
I realize I can add .Include() calls to my initial cache-fill statement. I want to avoid doing this because in my actual environment I have a large number of relations and a large number of lists I'm populating up front. I'm caching largely to keep Linq from generating overly-complicated nested SQL statements that tend to bog down my database server.
I also realize I can modify my query so as to do an in-memory join:
bool activeCompanies = allCompanies.Where
(
company => allEmployees.Any(employee => employee.CompanyID == company.ID)
);
I'm trying to avoid doing such a rewrite, because the actual business logic gets rather involved. Using Linq statements has significantly improved the readability of this logic, and I'd prefer not to lose that if at all possible.
So my question is this: can I connect them together manually somehow, in the way that the Entity Framework would connect them?
I'd like to continue to use the .Any() operator, but I want it to examine only the objects I have in memory in my dbContext - without going back to the database repeatedly.

Entity Framework Many to Many works but Include does not

I have a typical many-to-many relationship with these 3 tables
[Post] (
[PostId] int, (PK)
[Content] nvarchar(max)
...
)
[Tag] (
[TagId] int, (PK)
[Name] nvarchar
...
)
[TagPost] (
[TagId] int, (PK, FK)
[PostId] int (PK, FK)
)
And, TagId and PostId are the PK and FK set on the tables accordingly etc. Then I have these classes and mapping in c#
public class Post {
public Post()
{
this.Tags = new HashSet<Tag>();
}
[Key]
public int PostId { get; set; }
...
public virtual ICollection<Tag> Tags { get; private set; }
}
public class Tag {
public Tag()
{
this.Posts = new HashSet<Post>();
}
[Key]
public int TagId { get; set; }
...
public virtual ICollection<Post> Posts { get; private set; }
}
internal class MyDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Post>().ToTable("Post");
modelBuilder.Entity<Tag>().ToTable("Tag");
modelBuilder.Entity<Post>()
.HasMany(x => x.Tags)
.WithMany(x => x.Posts)
.Map(x =>
{
x.ToTable("TagPost");
x.MapLeftKey("PostId");
x.MapRightKey("TagId");
});
}
Then I have this code to query them
var list = (from p in ctx.Posts.Include(p => p.Tags)
from t in p.Tags
where ... // some of my filter conditions
select p).ToList();
This join does return the posts I was looking for, however the returned posts don't their associated tags filled in even though I have the Include there. Could someone help point out what I'm missing so that I could have the tags also return with the posts?
Thanks a lot.
The double from is a manual Join which causes the Include to be ignored as mentioned here and here. Include is also ignored for other LINQ methods like grouping and projections.
Relationship fixup generally does not work for many-to-many relationships, only for relationships which have at least one single reference at one of the ends - one-to-many or one-to-one. If you project the Posts and related Tags into another type (anonymous or named) the data will be loaded correctly but because the relationship is many-to-many EF won't create the relationship in memory automatically so that the post.Tags collection will stay empty.
To get the Include working you must remove the second from from your query and apply the where clause directly to the Post entity parameter, for example like so:
var list = (from p in ctx.Posts.Include(p => p.Tags)
where p.Tags.Any(t => t.TagId == 1)
select p).ToList();
The filter by a Tag property is specified in the expression passed into .Any which is an expression with a Tag (t) as parameter.
try selecting everything into an anonymous object (something like this)
var list = (
from p in ctx.Posts
from t in p.Tags
where ... // some of my filter conditions
select new {
Posts = p,
Tags = p.Tags
})
.ToList();
Based on the feedback to my initial answer and the fact that EF can find the related entities but it is failing to populate the Tags collection I believe the issue lies in the definition of the Tags entity in the Post class.
Try removing the Hashset<> initialiser from the constructors and private from the set declaration:
public virtual ICollection<Tag> Tags { get; set; }

Coalesce fields in a .net MVC 4 model without getting "Only initializers, entity members, and entity navigation properties are supported" from LINQ

The answer to this question gave rise to this other question: How to use LINQ expressions as static members of classes in queries when the class is related multiple times to a second class
I have an existing ASP.net MVC 4 site which I need to modify.
The core entity within this site are Items that are for sale, which are created by several different companies and divided into several categories. My task is to allow each company its own optional alias for the global categories. Getting the two categories set up in the database and model was no problem, making the application use the new optional alias when it exists and default to the global otherwise is where I'm struggling to find the optimal approach.
Adding a coalesce statement to every LINQ query will clearly work, but there are several dozen locations where this logic would need to exist and it would be preferable to keep this logic in one place for when the inevitable changes come.
The following code is my attempt to store the coalesce in the model, but this causes the "Only initializers, entity members, and entity navigation properties are supported." error to be thrown when the LINQ query is executed. I'm unsure how I could achieve something similar with a different method that is more LINQ friendly.
Model:
public class Item
{
[StringLength(10)]
[Key]
public String ItemId { get; set; }
public String CompanyId { get; set; }
public Int32 CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual GlobalCategory GlobalCategory { get; set; }
[ForeignKey("CompanyId, CategoryId")]
public virtual CompanyCategory CompanyCategory { get; set; }
public String PreferredCategoryName
{
get{
return (CompanyCategory.CategoryAlias == null || CompanyCategory.CategoryAlias == "") ? GlobalCategory.CategoryName : CompanyCategory.CategoryAlias;
}
}
}
Controller LINQ examples:
var categories = (from i in db.Items
where i.CompanyId == siteCompanyId
orderby i.PreferredCategoryName
select i.PreferredCategoryName).Distinct();
var itemsInCategory = (from i in db.Items
where i.CompanyId == siteCompanyId
&& i.PreferredCategoryName == categoryName
select i);
For one you are using a compiled function (getPreferredCategoryName) in the query, unless EF knows how to translate that you are in trouble.
Try the following in item definition:
public static Expression<Func<Item,String>> PreferredCategoryName
{
get
{
return i => (i.CompanyCategory.CategoryAlias == null || i.CompanyCategory.CategoryAlias == "") ?
i.GlobalCategory.CategoryName :
i.CompanyCategory.CategoryAlias;
}
}
Which is used as follows:
var categories = db.Items.Where(i => i.CompanyID == siteCompanyId)
.OrderBy(Item.PreferredCategoryName)
.Select(Item.PreferredCategoryName)
.Distinct();
This should work as you have a generically available uncompiled expression tree that EF can then parse.

Linq expression for filtered collection of collections?

I'm hoping this will be a rather simple question for anyone who's good at Linq. I'm struggling to come up with the right Linq expression for the following. I'm able to hack something to get the results, but I'm sure there's a proper and simple Linq way to do it, I'm just not good enough at Linq yet...
I have a database accessed through Entity Framework. It has a number of Tasks. Each Task has a collection of TimeSegments. The TimeSegments have Date and Employee properties.
What I want is to be able to get the tasks for a certain employee and a certain month and the timesegments for each task for that same month and employee.
Again, the tasks do not in themselves have month nor date information, but they do by the TimeSegments associated with each task.
Very simplified it looks sort of like this:
public class Model //Simplified representation of the Entity Framework model
{
public List<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public List<TimeSegment> TimeSegments { get; set; }
public Customer Customer { get; set; }
}
public class TimeSegment
{
public int Id { get; set; }
public string Date { get; set; }
public Employee Employee { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
So how do I do this as simply as possible with Linq? I.e. tasks and associated timesegments for a certain month and employee. I would also like to be able to get it by Customer BTW...
This is the simplest thing I could come up with:
var tasksWithSegments =
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
group segment by segment.Task into result
select new
{
Task = result.Key,
TimeSegments = result.ToArray()
};
Please note that you might have to add some properties to your model, such as Model.TimeSegment and TimeSegment.Task.
The trick with LINQ queries often is to start at the right collection. In this case the ideal starting point is TimeSegments.
ps. I'm not sure whether Date.Month == month will actually work with EF, but I think it will (with EF 4.0 that is).
Update:
Could you show how to extend this
query and get the tasks for a
particular Customer as well?
I'm not sure what you mean, but you can for instance filter the previous queryable like this:
var tasksWithSegmentsForCustomers =
from taskWithSegments in tasksWithSegments
where taskWithSegments.Task.Customer.Id == customerId
select taskWithSegments;
Can I get the return type to be a list
of Tasks with a list of TimeSegments
if I have this in a method?
Again, not sure what you exactly want, but if you want two separate lists that have no relation, you can do this:
List<Task> tasks = (
from taskWithSegments in tasksWithSegments
select taskWithSegments.Task).ToList();
List<TimeSegments> segments = (
from taskWithSegments in tasksWithSegments
from segment in taskWithSegments.Segments
select segment).ToList();
Of course, if this is what you need, than it might be easier to rewrite the original query to something like this:
List<TimeSegment> segments = (
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
select segment).ToList();
List<Task> allTasks =
segments.Select(s => s.Task).Distinct().ToList();
Once you got the hang of writing LINQ queries, there is no way you want to go back to writing SQL statements or old-fashion foreach statements.
Think LINQ!!!
What I want is to be able to get the
tasks for a certain employee and a
certain month and the timesegments for
each task for that same month and
employee.
This will select tasks from an instance of Model where the task has at least one time segment that in the requested month for the requested employee (untested):
Model model = new Model();
tasks = model.Tasks.Where(t => t.TimeSegments.Any(ts => ts.Employee.Id = requestedId && Convert.ToDate(ts.Date).Month == requestedMonth));

Resources