I have a domain class like below :
public class Employee
{
public int EmployeeId { get; set; }
public int DeptId { get; set; }
}
public class Transaction
{
public int TRID { get; set; }
public int EmployeeId { get; set; }
public string Status { get; set; }
}
Now I want to get all employees from the EmployeeTable for DeptId = 100. I want to calculate Pending status for those employees whose transactions are pending.
So if employee records are found in Transactions table then just want to return a column saying whether employee has any pending transactions or not)
Query :
var t = (from e in _employeeRepository.GetAll() //returns IQueryable<Employee>
where e.DeptId == 100
from t in _transactionRepository.GetAll().Where(t => t.EmployeeId == e.EmployeeId)
select new
{
IsPendingTransaction = (t != null && t.Status != "Done") ? true : false,
}).ToList();
Error : LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[Transaction] GetAll()' method, and this
method cannot be translated into a store expression."}
Sql Query :
SELECT e.*
(CASE WHEN (t.EmployeeId is not null and t.Status <> 'Done')
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
End) as IsPendingTransaction
FROM Employee e OUTER APPLY
(SELECT t.*
FROM Transactions t
WHERE e.EmployeeId = t.EmployeeId
) t
WHERE e.DeptId = 100;
The issue is that when you work within IQueryable, every statement inside that Linq expression must be understood by EF to be able to be translated to SQL.
Your first repository call returns an IQueryable<Employee> which you are trying to extend by telling it to join on some code called "_transactionRepository.GetAll()" EF doesn't know what this is, it doesn't correlate to mapped DbSets or properties on entities...
If your Transaction entity has a navigation property back to Employee (which it should) you should be able to accomplish what you want using just the TransactionRepository with something like:
var t = _transactionRepository.GetAll()
.Where(t => t.Employee.DeptId == 100)
.Select(t => new
{
IsPendingTransaction = (t != null && t.Status != "Done") ? true : false
}).ToList();
Using IQueryable<TEntity> in a repository pattern can be quite powerful, however I don't recommend adopting a Generic repository pattern as it just serves to fragment your thinking when working with entities and their relationships with one another, allowing EF to manage the resulting SQL without you resorting to pre-emptively trying to do the joining yourself, often causing conflicts with what EF is capable of working out itself.
Edit: Ok, from your description to get a list of employees with a flag if they have a pending transaction: That would be back at the Employee level with a query something like:
var employees = _employeeRepository.GetAll()
.Where(e => e.DeptId == 100)
.Select(e =>
{
Employee = e,
HasPendingTransaction = e.Transactions.Any(t => t.Status != "Done")
}).ToList();
Or projected to a ViewModel to embed the HasPendingTransaction alongside the Employee details:
var employees = _employeeRepository.GetAll()
.Where(e => e.DeptId == 100)
.Select(e => new EmployeeDetailsViewModel
{
EmployeeId = e.EmployeeId,
Name = e.Name,
// include other relevent details needed for the view...
HasPendingTransaction = e.Transactions.Any(t => t.Status != "Done")
}).ToList();
The advantage of projection is you can build more efficient / faster queries that reduce the amount of data sent over the wire and avoid issues like lazy load trips if you try to serialize entities to the view.
Fix Transaction class
public class Transaction
{
public int TRID { get; set; }
public string Status { get; set; }
public int EmployeeId { get; set; }
public virtual Employee Employee { get; set; }
}
It is not the best idea to have a separate repository for each entity since query usually consists from several entities. It is better to make a join using dbcontext then several repository queries as you trying to do. Don't try to create a base generic repository also. Sooner or later you will see that is is not very helpfull. So add in one of your repositories (probably EmployeeRepository) query like this
var employees= dbcontext.Transactions
.Where(t=> t.Employee.DeptId == 100 && t.EmployeeId==employeeId)
.Select (t=> new {
EmployeeName= t.Employee.Name,
IsPendingTransaction = (t.Status != null && t.Status != "Done") ? true : false}).ToList()
Related
I have a DbSet class:
public class Manufacturer
{
public Guid Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
I know I can use Skip() and Take() to get limited manufacturers. But my requirement is to get limited Products of all the manufacturers. I'm using something like this but it's not working
var manufacturers = await _context.Manufacturers.Where(x => x.Products.Take(10))
.ToListAsync();
PS: I'm using Lazy Loading (Not eager loading)
Compile error is:
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable<Domain.Product>' to 'bool'
Cannot convert lambda expression to intended delegate type because
some of the return types in the block are not implicitly convertible
to the delegate return type
How can I achieve to get all the manufacturers but limited products in them?
I believe there is no way to do this directly with a queryable source. You can manage it in memory.
var manufacturers = await _context.Manufacturers.Include(m => m.Products).ToListAsync();
foreach(var m in manufacturers)
{
m.Products = m.Products.Take(10).ToList();
}
This will get all products for each manufacturer from the DB and then keep only the first 10.
You can load the Manufacturer entity without the Product list first (so without an Include() call) and then run a separate query to load only the products you want for a specific Manufacturer entity. EF will automatically update the navigation properties. See the following example (authors can have multiple posts in this example):
using (var context = new MyContext())
{
Author author = context.Author.First();
Console.WriteLine(context.Post.Where(it => it.Author == author).Count());
context.Post.Where(it => it.Author == author).Take(2).ToList();
Console.WriteLine(author.Posts.Count());
}
This will generate the following output:
3
2
Even though there are three entries available in my test database, only two are actually read. See the generated SQL queries:
For the Author author = context.Author.First(); line:
SELECT `a`.`Id`, `a`.`Name`
FROM `Author` AS `a`
LIMIT 1
For the context.Post.Where(it => it.Author == author).Count() line:
SELECT COUNT(*)
FROM `Post` AS `p`
INNER JOIN `Author` AS `a` ON `p`.`AuthorId` = `a`.`Id`
WHERE `a`.`Id` = 1
For the context.Post.Where(it => it.Author == author).Take(2).ToList(); line:
SELECT `p`.`Id`, `p`.`AuthorId`, `p`.`Content`
FROM `Post` AS `p`
INNER JOIN `Author` AS `a` ON `p`.`AuthorId` = `a`.`Id`
WHERE `a`.`Id` = 1
LIMIT 2
However, you have to do this trick for each individual Manufacturer entity, that it loads only ten associated Product entities. This can result in 1+N SELECT queries.
Try the longer way:
_await _context.Manufacturers.Select(x =>
{
x.Products = x.Products.Take(10).ToList();
return x;
}).ToListAsync();
I would like to return some data from 2 related tables. I have a one to many relationship. One WebLead can have many Pricing records. I would like to return the data for the WebLead and the data for most recent record inserted into the Pricing table.
I am new to LINQ and EF. Here is what I have so far but this is only returning the WebLeads table data...What am I missing? Do I need to add a FirstOrDefault for the Pricing table?
var priceRefi = db.WebLeads.Include(p => p.Pricings)
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.LeadStatus.Equals("Priced");
then to populate the view model:
PricedRefiLeads = priceRefi.ToList(),
UPDATE: I am sorry I left so much out. I updated my query to the following (LoanAgent is just a string parameter)
var priceRefi = from lead in db.WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new LeadWithLastPricing()
{
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
I then want to take the results of that query and return it as a list to my view model:
var viewModel = new PipelineViewModel
{
////
PricedRefiLeads = priceRefi.ToList(),
}
I am seeing the following error on the priceRefi.ToList():
Cannot implicitly convert type
'System.Collections.Generic.List(LoanModule.ViewModels.LeadWithLastPricing)'
to 'System.Collections.Generic.List(LoanModule.Models.WebLead)
I am new to MVC. As I read this error, I understand that I must be missing something in my PipelineViewModel but I am not sure what that is.
In PipelineViewModel, I do have:
public List<WebLead> PricedRefiLeads { get; set; }
What am I missing? Forgive me if I left information out, I am struggling to wrap my head around this.
I am using a number of assumptions, for information not specifically mentioned in your question:
LoanAgent is a (local) string variable representing the agent you want to filter on.
Pricing has a field named PricingDate that is of type DateTime.
Then you can do it like this:
// I am assuming a Pricing has a DateTime field named "PricingDate"
var priceRefi =
from lead in WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
Note that this returns an anonymous object as the projection result. If you want to pass this result on, you should create a class:
public class LeadWithLastPricing
{
public Lead Lead { get; set; }
public Pricing LastPricing { get; set; }
}
And do the select part like this:
// ...
select new LeadWithLastPricing() {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
For your second error, change this:
public List<WebLead> PricedRefiLeads { get; set; }
To
public List<LeadWithLastPricing> PricedRefiLeads { get; set; }
And use it like:
var viewModel = new PipelineViewModel
{
PricedRefiLeads = priceRefi.ToList(),
}
You can try:
var query =
from l in db.WebLeads
let maxDate = l.Pricings.Max(p => p.InsertDate)
where l.LoanAgentID == someLoanAgentID
&& l.LeadStatus == "Priced"
select new { Lead = l, Pricing = l.Pricings.FirstOrDefault(x => x.InsertDate == maxDate) };
This will give objects with two properties: Lead and Pricing which are WebLead and Pricings objects. Please forgive syntax errors this is streight out of notepad.
Edit: I suppose I should tell you how to use the result object:
foreach (var MyLead in query)
{
string customerName = MyLead.Lead.CustomerName; // i.e. some property of WebLead
DateTime InsertDate = MyLead.Pricing.InsertDate; // i.e. some property of Pricings
}
You can project directly into a WebLead instead of creating a new class, by doing the following:
var priceRefi = db.WebLeads
.Include(p => p.Pricings)
.Where(l => l.LoanAgent == LoanAgent)
.Where(l => l.LeadStatus == "Priced")
.Select(lead=>new WebLead {
lead.LoanAgent,
lead.LeadStatus,
...,
Pricings=lead.Pricings.OrderByDescending(x=>x.PricingDate).Take(1)});
SETUP
I have an "Order" class and an "OrderDetail" class.
The Order class has a property called "OrderDetails" of type IQueryable.
I have a method in each class called "GetData" and this method returns IQueryable of the class it is in.
The "GetData" methods are wrappers around the Entities that are used to transform the Entities into friendlier .NET classes / properties.
For example: The OrderDetail Entity has a "PullDate" in the database as Char(10) but I want the OrderDetail Class to have a property called "PullDate" that is of type DateTime in the class.
CODE
public class Order
{
[Key]
public int ID { get; set; }
public IQueryable<OrderDetail> OrderDetails { get; set; }
public static IQueryable<Order> GetData()
{
IQueryable<Order> orders;
var db = new OrderEntities();
// NOTE: This code will work
try
{
var data =
(from O in db.Orders
where O.OrderID == 1
select new Order
{
ID = O.OrderID,
OrderDetails =
(from OD in db.OrderDetails
where OD.OrderID == O.OrderID
select new OrderDetail
{
ID = OD.OrderDetailID,
OrderID = OD.OrderID,
PullDate = OD.PullDate == "00000000" ?
(DateTime?)null : db.yyyyMMddToDate(OD.PullDate),
Description = OD.Desc
})
});
orders = data.AsQueryable();
var orderList = orders.ToList();
}
catch (Exception ex)
{
throw;
}
// NOTE: This code will NOT work
try
{
var data = (from O in db.Orders
where O.OrderID == 1
select new Order
{
ID = O.OrderID,
OrderDetails = (from OD in OrderDetail.GetData(db)
where OD.OrderID == O.OrderID
select OD)
});
orders = data.AsQueryable();
var orderList = orders.ToList();
}
catch (Exception ex)
{
throw;
}
return orders;
}
}
public class OrderDetail
{
[Key]
public int ID { get; set; }
public int OrderID { get; set; }
public DateTime? PullDate { get; set; }
public string Description { get; set; }
public static IQueryable<OrderDetail> GetData(OrderEntities db)
{
IQueryable<OrderDetail> orderDetails;
var data = (from OD in db.OrderDetails
select new OrderDetail
{
ID = OD.OrderDetailID,
OrderID = OD.OrderID,
PullDate = OD.PullDate == "00000000" ?
(DateTime?)null : db.yyyyMMddToDate(OD.PullDate),
Description = OD.Desc
});
orderDetails = data.AsQueryable();
var orderList = orderDetails.ToList();
return orderDetails;
}
}
ERROR
LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[Models.OrderDetail] GetData(Models.OrderEntities)' method, and this method cannot be translated into a store expression.
Request
I would like the Order.GetData method to call the OrderDetail.GetData method using LINQ.
I need to "join" the GetData methods together or "sub select" the OrderDetail.GetData while inside the Order.GetData class.
Both of the classes are querying EntityFramework inside of their GetData methods.
Projection is a requirement.
My goal is to create "Reusable" methods like "GetData" in my DTO classes that will contain specific SQL / Entity logic.
For example, I am using a lot of custom SQL functions like "db.yyyyMMddToDate" in my DTO classes to transform the Entities into something more object / .NET friendly and I don't want to "retype" all that logic each time I need to "join / sub select" data from entities.
In LINQ to Objects this would be the same as joining two different lists from different classes.
But it seems that LINQ to Entity does not know how to join methods from other classes even if the method is marked as Queryable.
I understand that LINQ to Entity is treating the "GetData" methods as if they were SQL functions but I need a way to tell LINQ to Entity that these are just more Entities to join together to get the results.
You get this error because, when dealing with LINQ to Entities, you are not passing in lambdas to the LINQ operators like you do in LINQ to Object. You are passing expression trees. The code that you write in those queries will never be executed. It will be interpreted by Entity Framework as a series of instruction to translate into an other language like SQL.
The GetData method doesn't mean anything in SQL. In fact, it doesn't mean anything outside of your own program. This is why LINQ to Entities do not recognize your method as a valid construct.
To access the details of an order in a scenario like yours, you need to build your query so that EF knows to include the OrderDetails entities inside the primary query using the Include operator. When doing this, EF expects your object to have a property similar to this one:
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
This is how EF will know where to put the resulting objects from queries that includes those elements.
var ctx = new DatabaseContext();
var query = ctx.Orders
.Where(o => o.OrderId == 1)
.Include(o => o.OrderDetails);
This query will asks the remote server for all Orders, with an OrderId of 1, with all its matching OrderDetails. EF will then automatically create all of the related client side entities for you.
Here's the official documentation on EF about this topic: http://msdn.microsoft.com/en-ca/data/jj574232.aspx
I am stuck on doing a hierarchical query in LINQ - I am on my first ASP.NET project ever, so bear over with my lack of knowledge and experience. I am basically doing the project on EF6, C#, and MVC 5.
So, I can't figure out how to get the following hierarchical data out.
I have an employee table, an employeeMap table, and a Goal table. EmployeeMap maps goals to employees. Goals are hierarchical so, a goal has a parent goal in an unary relationship, here my Goal class a little simplified:
public class Goal
{
public int ID { get; set; }
public string Name { get; set; }
public int? ParentID { get; set; }
public virtual Goal Parent { get; set; }
}
I need a list of goals mapped to an employee, and all the parent goals thereof. I can get the goals mapped to the employee, and the parent goal thereof, but can't get the parents parent and so on up the hierarchy to the top, where parentID would be null. Here's my query getting the goals and the direct parent.
viewModel.EmpGoals = (
from g in db.Goals
join m in db.EmployeeMaps on g.ID equals m.GoalID
join e in db.Employees on m.EmployeeID equals e.ID
where m.EmployeeID == id.Value
select new EmployeeGoal
{
EmployeeID = e.ID,
LastName = e.LastName,
FirstName = e.FirstName,
GoalID = g.ID,
Name = g.Name,
ParentID = g.ParentID,
Parent = g.Parent,
WeightPct = m.WeightPct,
Locked = m.State.Equals(1),
Activities = g.Activities
}).ToList();
}
So I guess I need a hierarchical query, recursively running up to all parents and return each parent ( or at least just the very top of the parent tree, or root maybe rather), but how can I do that using LINQ, or should I consider some raw SQL to give me this back?
Thanks :)
Does it have to be a single query? Maybe return your list after calling a method from your data layer service:
ExampleDataLayerService dlSvc = new ExampleDataLayerService();
viewModel.EmpGoals = dlSvc.GetEmpGoalList(id.Value);
Service layer method:
public List<EmployeeGoal> GetEmpGoalList(int empID)
{
//Get employee info
var empInfo = db.Employees.Where(x => x.ID == empID).Select(x => new { ID = x.ID, LastName = x.LastName, Firstname = x.FirstName }).FirstOrDefault();
//Get initial bottom tier list of employee goals
List<int> goalIdList = db.EmployeeMaps.Where(x => x.EmployeeID == empID).Select(x => x.GoalID).ToList();
List<EmployeeGoal> empGoalList = new List<EmployeeGoal>();
List<int> usedGoalList = new List<int>();
foreach (var goal in goalIdList)
{
var tempID = goal;
while (tempID != 0 && tempID != null)
{
var gmData = (from g in db.Goals
join m in db.EmployeeMaps.Where(m => m.EmployeeID == empInfo.ID) on g.ID equals m.GoalID into m_g
from mg in m_g.DefaultIfEmpty()
where g.Goals == tempID
select new EmployeeGoal
{
EmployeeID = empInfo.ID,
LastName = empInfo.LastName,
FirstName = empInfo.FirstName,
GoalID = g.ID,
Name = g.Name,
ParentID = g.ParentID,
Parent = g.Parent,
WeightPct = (mg == null) ? 0 : mg.WeightPct,
Locked = (mg == null) ? 0 : mg.State.Equals(1),
Activities = g.Activities
}).FirstOrDefault();
if (!usedGoalList.Contains(gmData.GoalID))
{
empGoalList.Add(gmData);
UsedGoalList.Add(gmData.GoalID);
}
tempID = gmData.ParentId;
}
}
return empGoalList;
}
Code is off the top of my head an untested so may not run as is. If desired you could also add some meta data to determine what "tier" each goal is if you need to sort the goal list from root downwards or something like that. Also this solution may not work as it will be much less efficient than a single LINQ query as this one has multiple hits the DB. To save DB hits you can also just build the tables in memory first and query from those if that's preferred.
Hope it helps or at least gives some ideas for a workable solution.
I have two models, Benefit and SchemeName
Benefit -
[Key]
public int BenefitID { get; set; }
public string BenefitName { get; set; }
public string BenefitDescription { get; set; }
public virtual ICollection<SchemeName> SchemeNames { get; set; }
SchemeName
[Key]
public int SchemeNameID { get; set; }
public string Name { get; set; }
public virtual ICollection<Benefit> Benefits { get; set; }
This has created three tables in the database Benefits, SchemeNames and a joining table called SchemeNameBenefits.
I am trying to populate a droplownlist that contains only the SchemeNames associated with a certain Benefit but am not sure how I can do this, can I reference the join table in my code?
I started with the following (which returns all SchemeNames)
private void PopulatePensionSchemeName(object selectedPensionSchemeName = null)
{
var schemeNameQuery = from d in db.SchemeNames
orderby d.SchemeNameID
select d;
ViewBag.PensionSchemeNameID = new SelectList(schemeNameQuery, "SchemeNameID", "Name", selectedPensionSchemeName);
}
But I'm not sure how I can add this clause. Any pointers?
You'll need the key of the Benefit object you want the SchemeNames for. The query you're probably looking for is:-
var benefitId = // However you get your benefit Id
var schemaNameQuery = from b in db.Benefits
from s in b.SchemeNames
where b.BenefitId == benefitId
select s;
Or in the extension method syntax:-
var schemaNameQuery = db.Benefits.Where(b.BenefitId == benefitId)
.SelectMany(b => b.SchemeNames);
Which produces the following SQL:-
SELECT ...
FROM [dbo].[SchemeNameBenefits] AS [Extent1]
INNER JOIN [dbo].[SchemeNames] AS [Extent2]
ON [Extent1].[SchemeName_Id] = [Extent2].[SchemeNameId]
WHERE [Extent1].[Benefit_Id] = #p__linq__0
Alternately you can use:-
var benefitId = // However you get your benefit Id
var schemeNameQuery = from d in db.SchemeNames
where d.Benefits.Any(x => x.Id == benefitId)
orderby d.SchemeNameId
select d;
This produces the following SQL:-
SELECT ...
FROM ( SELECT ... FROM [dbo].[SchemeNames] AS [Extent1]
WHERE EXISTS (SELECT 1 AS [C1]
FROM [dbo].[SchemeNameBenefits] AS [Extent2]
WHERE ([Extent1].[SchemeNameId] = [Extent2].[SchemeName_Id])
AND ([Extent2].[Benefit_Id] = #p__linq__0)))
AS ...
ORDER BY [Project2].[Id] ASC
Note that in both cases the generated SQL references your junction table even though it isn't part of your EF model.
If you already have the Benefit object, of course, you can get its SchemeNames more simply by using:-
var schemeNameQuery = benefit.SchemeNames;
I would do this by using a Junction Table. The Junction Table is your Joining Table. It will consist of two foreign Keys, SchemeNameID & BenefitID.
Check out this website for more on Junction Tables:
http://megocode3.wordpress.com/2008/01/04/understanding-a-sql-junction-table/
It helped me out a lot.
[Disclaimer] Conditional on there existing or adding the SchemeNameBenefits model the following should work.
So it seems like the SchemeNameBenefits table is a many-to-many map, in this case use the benefit ID to get a collection of scheme ID's
var schemeIds = db.SchemeNamesBenefits.Where(map => map.BenefitID == id)
.Select(map => map.SchemeNameID).ToArray();
Then pull back all the scheme name information for these scheme ID's
var result = db.SchemeNames.Where(scheme => schemeIds.Contains(scheme.SchemeNameID))
.OrderBy(scheme => scheme.SchemeNameId)
.Select(scheme => scheme.Name).ToArray();
Or in one query
var result = db.SchemeNamesBenefits.Where(map => map.BenefitID == id)
.SelectMany(map => db.SchemeNames
.Where(scheme => map.SchemeNameID == scheme.SchemeNameID)
.OrderBy(scheme => scheme.SchemeNameId)
.Select(scheme => scheme.Name)
.AsEnumerable())
.ToArray()