I have two tables, CaseProductLinks and Products as shown here:
I am trying to get the following information using LINQ:
Here's what I would do in SQL:
SELECT
p.ProductID, p.ProductName,
COUNT(c.CaseID) AS Frequency
FROM
CaseProductLinks c
JOIN
Products p ON c.ProductID = p.ProductID
GROUP BY
p.ProductID
Here's what I have in C# so far which throws a "System.InvalidOperationException":
var objs = from p in _db.CaseProductLinks
join c in _db.Cases on p.ProductId equals c.ProductId into g
select new S_Link
{
ProductID = p.ProductId,
ProductName = p.Product,
Frequency = g.Count() //Not sure what to do here
};
If you've set your navigation up correctly (i.e. a Product has an ICollection<CaseProductLink> CaseProductLinks) you can simply do:
var r = _db.Products.Select(p =>
new
{
p.ProductId,
p.ProductName,
Frequency = p.CaseProductLinks.Count()
}
);
Here's what I would do in SQL:
If you're quite used to SQL it can be a step to pull yourself away from thinking in those ways and into the ways that EF is designed to abstract over them. One of the big plus points of EF is in telling it how your database tables/entities relate to each other, and then it will form the joins. It's not some dumb device that has to be pummeled into making every join and group that it does; if it knew there was 1 Product with Many CaseProductLink then it can write the join/group that counts the number of relates CPL per P simply by accessing the collection on Product with an operation that aggregates (Count)
If you don't have this nav set up, then I really would recommend to do so, as it's a big chunk of the beauty of EF that makes C# side code nice to work with
I Test Below Code And Work Fine.Please check it
public class Productlinks
{
public int CaseId { get; set; }
public int ProductId { get; set; }
}
public class Products
{
public int ProductId { get; set; }
public string ProductName { get; set; }
}
static void Main(string[] args)
{
var products = new List<Products>()
{
new Products(){ ProductId=1, ProductName="A"},
new Products(){ ProductId=2, ProductName="B"},
new Products(){ ProductId=3, ProductName="C"},
};
var links = new List<Productlinks>()
{
new Productlinks(){ CaseId=1, ProductId=1 },
new Productlinks(){ CaseId=3, ProductId=2 },
new Productlinks(){ CaseId=3, ProductId=2 },
new Productlinks(){ CaseId=4, ProductId=3 },
};
var objs = from p in products
join c in links on p.ProductId equals c.ProductId into g
select new
{
ProductID = p.ProductId,
ProductName = p.ProductName,
Frequency = g.Count()
};
}
Related
I have a nested data model and I'd like to get aggregated data from it grouped by a top level property.
My models for example:
public class Scan {
public long Id {get; set;}
public int ProjectId { get; set; }
public int ScanUserId { get; set; }
public ICollection<ScanItem>? Items { get; set; }
}
public class ScanItem
{
public long Id { get; set; }
public long InventoryScanId { get; set; }
public double Qty { get; set; }
}
I'd like to get all Scans grouped by Scan.ScanUserId and then then get the sum of ScanItems.Qty for example per user. My query looks ike this and EF gives the following error:
Processing of the LINQ expression 'AsQueryable((Unhandled
parameter: x).Items)' by 'NavigationExpandingExpressionVisitor' failed
from scan in Scans
.Include(x=>x.ScanUser)
.Include(x=>x.Items)
group scan by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
select new
{
UserId = g.Key.Id,
Name = g.Key.Name,
LastSyncTime = g.Max(x => x.ScanDate),
ScanItems = g.Sum(x=>x.Items.Sum(i=>i.Qty))
}
How can I run aggregate functions on the properties of the nested table without evaluating it on the client side?
EF Core still can't translate nested aggregates on GroupBy result (grouping).
You have to pre calculate the nested aggregates in advance by utilizing the element selector of GroupBy (or element in query syntax group element by key):
from scan in Scans
group new { scan.ScanDate, Qty = scan.Items.Sum(i => i.Qty) } // <--
by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
select new
{
UserId = g.Key.Id,
Name = g.Key.Name,
LastSyncTime = g.Max(x => x.ScanDate),
ScanItems = g.Sum(x => x.Qty) // <--
}
**Update: ** for SqlServer the above LINQ query translates to SQL query like this:
SELECT [s1].[Id] AS [UserId], [s1].[Name], MAX([s0].[ScanDate]) AS [LastSyncTime], SUM((
SELECT SUM([s].[Qty])
FROM [ScanItem] AS [s]
WHERE [s0].[Id] = [s].[InventoryScanId])) AS [ScanItems]
FROM [Scan] AS [s0]
INNER JOIN [ScanUser] AS [s1] ON [s0].[ScanUserId] = [s1].[Id]
GROUP BY [s1].[Name], [s1].[Id]
which as mentioned in the comment generates SQL execution exception "Cannot perform an aggregate function on an expression containing an aggregate or a subquery.".
So you really need another approach - use left join to flatten the result set before grouping, and then perform the grouping / aggregates on that set:
from scan in Scans
from item in scan.Items.DefaultIfEmpty() // <-- left outer join
group new { scan, item } by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
select new
{
UserId = g.Key.Id,
Name = g.Key.Name,
LastSyncTime = g.Max(x => x.scan.ScanDate),
ScanItems = g.Sum(x => (double?)x.item.Qty) ?? 0
};
which now translates to hopefully valid SqlServer SQL query:
SELECT [s1].[Id] AS [UserId], [s1].[Name], MAX([s].[ScanDate]) AS [LastSyncTime], COALESCE(SUM([s0].[Qty]), 0.0E0) AS [ScanItems]
FROM [Scan] AS [s]
LEFT JOIN [ScanItem] AS [s0] ON [s].[Id] = [s0].[InventoryScanId]
INNER JOIN [ScanUser] AS [s1] ON [s].[ScanUserId] = [s1].[Id]
GROUP BY [s1].[Name], [s1].[Id]
I'm trying to convert the following Join statement into LINQ TO SQL or LINQ to Entity. I know how to join tables in either implementation; but, i'm struggling with the AND clause in the Join statement.
SELECT DISTINCT
p.LastName,
p.FirstName
FROM
dbo.Patient p INNER JOIN dbo.FormPat fp ON p.PatientID = fp.PatientID
INNER JOIN dbo.TxCyclePhase tcp ON fp.TxCyclePhase = tcp.TxCyclePhaseID AND tcp.Type = 2
As far as LINQ to SQL is concerned, I have the followings:
var query = (from p in Context.Set<Patient>().AsNoTracking()
join fp in Context.Set<PatientForm>().AsNoTracking() on p.Id equals fp.PatientId
join tcp in Context.Set<TxCyclePhase>().AsNoTracking() on new { fp.TxCyclePhaseId, seconProperty = true } equals new { tcp.Id, seconProperty = tcp.Type == 2 }
select new
{
p.FirstName,
p.LastName,
}).Distinct();
However, I'm getting an ArgumentNullException on the second join statement.
For the LINQ to Entity, I have the followings, however, this is giving me a distinct IQueryable of FormPat, instead of Patient.
var patients = Context.Set<Patient>().AsNoTracking()
.SelectMany(p => p.Forms)
.Where(fp => fp.Phase.Type == 2)
.Distinct();
As far as the LINQ to Entity is concerned, I was able to figure it out. I'd still like to know how to do it in LINQ to SQL tho.
I'm using the EF fluent API. My Patient object looks like:
public Patient()
{
Programs = new HashSet<Program>();
}
public virtual ICollection<PatientForm> Forms { get; set; }
My PatientForm object looks like:
public class PatientForm
{
public int FormId { get; set; }
public Patient CurrentPatient { get; set; }
public TxCyclePhase Phase { get; set; }
}
And the CyclePhase object looks like:
public TxCyclePhase()
{
this.FormPats = new HashSet<PatientForm>();
}
public int Id { get; set; }
public virtual ICollection<PatientForm> FormPats { get; set; }
In the entity configurations, I have the relationships set. So, in the repository, all I have to do is to use the Any() function when selecting the Patient forms.
var patients = Context.Set<Patient>().AsNoTracking()
.Where(p => p.Forms.Any(f => f.Phase.Type == 2))
.Distinct();
I am trying to join two object list and update the property(Revenue) in the first object (ProductReport) to be the same the one of the properties(Revenue) in the joined object (Transaction). The code is as blow:
using System.Collections.Generic;
using System.Linq;
namespace TestConsole
{
public class ProductReport
{
public string productName { get; set; }
public int PrdouctID { get; set; }
public double Revenue { get; set; }
}
public class Transaction
{
public double Revenue { get; set; }
public int PrdouctID { get; set; }
}
public class Program
{
static void Main(string[] args)
{
List<ProductReport> products = new List<ProductReport>();
ProductReport p = new ProductReport();
p.productName = "Sport Shoe";
p.PrdouctID = 1;
products.Add(p);
List<Transaction> transactions = new List<Transaction>();
Transaction t1 = new Transaction();
t1.PrdouctID = 1;
t1.Revenue = 100.0;
Transaction t2 = new Transaction();
t2.PrdouctID = 1;
t2.Revenue = 200.00;
transactions.Add(t1);
transactions.Add(t2);
var results = (from product in products
join transaction in transactions on product.PrdouctID equals transaction.PrdouctID into temp
from tran in temp.DefaultIfEmpty()
select new { product, tran }
).ToList();
results.ForEach(x => x.product.Revenue = x.tran.Revenue);
}
}
}
The code joins 2 object lists based on ProductID. I am expecting to see two ProductReport items in the results object with 100.00 and 200.00 revenue respectively. However, the foreach function somehow overwrite the revenue property in the first ProductReport. In the end, both ProductReport have 200.00 as the revenue. Can anyone help me with that and how to easily get the result i want
? Thanks.
You'll just need to change your join around
var results = (from p in products
join t in transactions on p.ProductID equals t.ProductId
select new ProductReport{
ProductName = p.productName,
ProductId = p.ProductID,
Revenue = t.Revenue
}).ToList();
You'll then have a list which has 2 entries for ProductId = 1 (Sport Shoe)
ProductName = "Sport Shoe"
ProductId = 1
Revenue = 200.00
ProductName = "Sport Shoe"
ProductId = 1
Revenue = 100.00
The foreach function somehow overwrite the revenue property in the first ProductReport. In the end, both ProductReport have 200.00 as the revenue.
In the following line x.product refers to the same instance.
results.ForEach(x => x.product.Revenue = x.tran.Revenue);
You have only one ProductReport object, and it can have only one value for its Revenue property. The Revenue property of this single instance cannot simultaneously be both 100.00 and 200.00. The first time you write to it, you set it to 100.00, the second time you write to it, you overwrite it to 200.00.
I am expecting to see two ProductReport items in the results object.
Your code creates only one ProductReport item. If you want to see two (or more) of them, then you need to create two (or more) objects. The following code leverages Select to do that for you. Whereas your ForEach mutates the original object, Select and the rest of the LINQ create a new object and do not mutate the original one.
var results =
from product in products
join transaction in transactions on product.PrdouctID equals transaction.PrdouctID
select new
{
product, transaction
};
var updated = results.Select(x => {
x.product.Revenue = x.transaction.Revenue;
return x.product;
});
And here it is as a Fiddle with this output:
Sport Shoe 100
Sport Shoe 200
I'm newer using C#, linq. I'm trying to add the UserName into a query to show it as part of a DataSource of a ListView, I have tested several way to joined, but always I m'receiving next error:
"Unable to create a constant value of type 'Web.Admin.system.User'. Only primitive types or enumeration types are supported in this context."
My code is:
//Entities
public class Category
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public string Description { get; set; }
public Guid CategoryId { get; set; }
public Guid UserId { get; set; }
}
//class added just for getting the user list (possibly, I do not need)
public class User
{
public Guid Id { get; set; }
public String Name { get; set; }
}
Here is my code preparing the filter
//retrieve the data from Order and Category
IQueryable<Order> orders = orderService.GetAllOrders();
IQueryable<Category> category = categoryService.GetAllCategories();
//obtain the users
MembershipUserCollection members = Membership.GetAllUsers();
// 1st option for managing users directly with memberShip variable
var memberShip = members.Cast<MembershipUser>().ToDictionary(m => m.ProviderUserKey, m => m.UserName).AsQueryable();
// 2nd option, I have added this code to see if I could manage the users as a list
List<User> users = new List<User>();
foreach (var _member in memberShip)
{
users.Add(new User { Id = (Guid)_member.Key, Name = _member.Value });
}
//Getting information to fill a listview
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
join u in users on i.UserId equals u.Id // 1st I tried to use memberShip directly but gave me error of types
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = u.Name
};
ListViewOrders.DataSource = DDLsource.ToList();
Here is where the Error is triggered, I'm trying to understand the error and do other solution, I tested the query like:
Example 2
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = (from u in users where u.Id == i.UserId select u.Name)
};
Example 3
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
join u in Membership.GetAllUsers().Cast<MembershipUser>() on i.UserId equals ((Guid)u.ProviderUserKey)
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = u.UserName
};
all with the same results, could someone give me a hand with my mistake will surely be very obvious. Thanks in advance
I would do something like this (sorry, untested code...):
var DDLsource =
from i in orders
join c in category on i.CategoryId equals c.Id
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
i.UserId,
UserName = ""
};
foreach(var ddl1 in DDLsource)
ddl1.UserName = Membership.GetUser(ddl1.UserId).Name;
I am having trouble figuring out how to traverse a one to many relasionship using LINQ-To-SQL in my asp.net site that uses EF 5. I have made the relationships in the class files but when I try to go from parent to child in my where clause I am not given a list of the child columns to filter on. Can anyone tell me what is wrong with my code, I am new to EF and LINQ.
Product.cs:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Category Category { get; set; }
}
}
Category.cs:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<Product> Products { get; set; }
}
Codebehind:
using (var db = new Compleate())
{
rpBooks.DataSource = (from c in db.Categories
where c.Products.Name == "Books"
select new
{
c.Name
}).ToList();
}
Do you want all products in the books category?
from p in db.Products
where p.Category.Name == "Books"
select new
{
p.Name
}
Or do you want to have all categories that contain products that are called called books?
from c in db.Categories
where c.Products.Contains( p => p.Name == "Books")
select new
{
c.Name
}
BTW, if you're only selecting the name, you can skip the anonymous type in the select part...
select p.name
Ok I had to update the codebhind to look like:
using (var db = new Compleate())
{
rpBooks.DataSource = (from c in db.Categories
join p in db.Products on c.ID equals p.id
where c.Products.Name == "Books"
select new
{
c.Name
}).ToList();
}
It should be name = c.Name it's not an issue with traversing, it's an issue with syntax, read the brief article on anonymous types here