LINQ - Group by with SUM IF - c#

Im trying to write in linq something which is easy (for me) in SQL. Any idea how to do something like this:
select Items.IdItem,
Items.Name,
count(1) as Quantity,
SUM(IF(State.IdStatus=1,1,0)) as Availible
from Items
inner join State
on Items.IdItem=State.IdItem
group by Items.IdItem
I wrote something like that:
var result = from items in _context.Items
join state in _context.State on items.IdItem equals State.IdItem
group items by { items.IdItem, items.Name } into g
select new { Name= g.Key.Name, IdItem=g.Key,IdItem, Quantity=g.Count(), Availible= ???? }
Any tips?

Provided you have set your relations in the database and navigational properties in your model (which is done by generators), then in Linq you seldom need joins (for tables that don't have a direct relation).
Second, you are not really after Sum() here, are you. Looking at your sum function and field name, it more looks like you are after "Is Available" check.
var result = from i in _context.Items
group i by i.IdItem into g
select new {
IdItem = g.Key,
Name = g.First().Name,
Quantity = g.Count(),
Available = g.Any(it => it.State.IdStatus == 1)
};
EDIT: if your Sum was intentional, then you can replace the Available part by (it is a bit, right?):
Available = g.Sum(it => it.State.IdStatus)
EDIT: This one is based on your data/model and SQL at top:
var result = from i in _context.Items
select new
{
i.IdItem,
i.Name,
Quantity = i.States.Count(),
Available = i.States.Count(x => x.IdStatus == 1)
};
Sample code and results:
string defaultConString = #"server=.\SQLExpress;Database=SampleDb;Trusted_Connection=yes;";
void Main()
{
var _context = new MyContext(defaultConString);
var result = from i in _context.Items
select new
{
i.IdItem,
i.Name,
Quantity = i.States.Count(),
Available = i.States.Count(x => x.IdStatus == 1)
};
result.Dump(); // linqPad
}
public class MyContext : DbContext
{
public MyContext(string connectionString)
: base(connectionString)
{ }
public DbSet<Item> Items { get; set; }
public DbSet<State> States { get; set; }
}
[Table("Items")]
public partial class Item
{
public Item()
{
this.States = new List<State>();
OnCreated();
}
[Key]
public virtual int IdItem { get; set; }
public virtual string Name { get; set; }
public virtual System.DateTime ImportDate { get; set; }
public virtual System.DateTime ReturnDate { get; set; }
public virtual IList<State> States { get; set; }
partial void OnCreated();
}
[Table("States")]
public partial class State
{
public State()
{
OnCreated();
}
[Key]
public virtual int IdState { get; set; }
public virtual string SmId { get; set; }
public virtual string Number { get; set; }
public virtual int IdItem { get; set; }
public virtual int IdStatus { get; set; }
public virtual Item Item { get; set; }
partial void OnCreated();
}
Result:
IdItem Name Quantity Available
1 Test01 3 2
2 Test02 2 1
3 Test03 1 0

Related

Simple query performance

Let's say i have 4 Tables A,B,C and D
What is the best way to get list of B to be in a dropdown list.
Condition is that each record should have associated list of D1
so that if B has no D1 records i should not show it in the dropdown list
D1 is a child class of D
How i did it:
// number 2 to specify the selected A Table normally it's a variable
var b_Per_A = Uow.B.GetAll().Where(x => x.A_Id == 2).ToList();
var b_list = new List<B>();
foreach (var b in b_list)
{
var d =
Uow.D1.GetAll().Where(x => x.C.B_Id == b.Id);
if (!d.IsNullOrEmpty()) // extension method that checks if a collection is null or empty
{
b_list.Add(b);
}
}
It works but it's slow
UPDATE:
The signature of GetAll() is
IQueryable<T> GetAll();
UPDATE 2:
My Model is
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<B> ChildBs { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public A ParentA { get; set; }
public IEnumerable<C> ChildCs { get; set; }
}
public class C
{
public int Id { get; set; }
public string Name { get; set; }
public B ParentB { get; set; }
public IEnumerable<D> ChildDs { get; set; }
}
public class D
{
public int Id { get; set; }
public string Name { get; set; }
public C ParentC { get; set; }
}
public class D1 : D
{
/// other properties
}
Given that you've confirmed you have navigation properties on your entity classes, this could be achieved in a single expression:
var result = Uow.B.GetAll()
.Where(b =>
b.ParentA.Id == 2 &&
b.ChildCs.Any() &&
b.ChildCs.SelectMany(c => c.ChildDs).Any())
.ToList();
This will push the filtering to your database rather than doing it in memory and should be a lot more efficient.
Also, I'm assuming that a parent navigation property will never be null and that child collections are always initialized.
Example here
put your var d = Uow.D.GetAll().Where(x => x.C.B_Id == b.Id); outside of your foreach. I also recommend you to use IEnumerable/ICollection instead of List and use for loop instead of foreach. Indexed looping is faster.

SQL multiple results with entity

I want to merge result of two SQL select statements into one.
Here is sample :
// class
public class _Product
{
public long ProductId { get; set; }
public string Description { get; set; }
}
public class _Company
{
public long CompanyId { get; set; }
public int posotion{ get; set; }
}
// C# entity
void runQyery()
{
string MultiQuery = "select ProductId, Description from product;"+
" select CompanyId, posotion from Company;";
_Product objPro = new _Product();
_Company objCom = new _Company();
// Is this right? What should I do now?
var result = _entities.Database.ExecuteSqlCommand(MultiQuery);
objPro = ...
objCom = ...
}
How can I finish my code? Is there any way to read some select on one trying?
Thanks for your attention

How to group multiple lists using LINQ - Sum and GroupBy

I have three generic lists and I am trying to combine and get them into one (List<One>). I am not sure how to use LINQ group by on different lists. I want to group by AccountCode, AccountDate, and SalesCode; and sum the amount.
public class One
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
public class Two
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
public class Three
{
public string AccountCode { get; set; }
public DateTime AccountDate { get; set; }
public string SalesCode { get; set; }
public decimal? Amount { get; set; }
}
List<One> oneList = new List<One>();
List<Two> twoList = new List<Two>();
List<Three> threeList = new List<Three>();
This is the sample query I have, which is not working. Note: I have not included List<Three>.
from first in oneList
join second in twoList
on first.AccountCode equals second.AccountCode
where
(first.AccountDate.Date == second.AccountDate.Date && first.SalesCode == second.SalesCode)
select new
{
first.AccountCode,
first.AccountDate,
first.SalesCode
first.Amount,
second.Amount
}).Distinct()
group bill by new { bill.AccountCode, bill.AccountDate, bill.SalesCode }
into groupedList
select new
{
groupedList.Key.UAN,
groupedList.Key.AccountDate,
Amount = groupedList.Sum(a => a.Amount)
};
As mentioned in the comment, the first thing you'll need is a common type so that the 3 lists can be joined into a single list. That could be a base class or an interface as I've shown here.
public interface IAccount
{
string AccountCode { get; set; }
DateTime AccountDate { get; set; }
string SalesCode { get; set; }
decimal? Amount { get; set; }
}
public class One : IAccount
{
// ...
}
public class Two : IAccount
{
// ...
}
public class Three : IAccount
{
// ...
}
Once that's in place, then it's easy to combine your 3 lists into one like this:
var allList = oneList.Cast<IAccount>().Union(twoList).Union(threeList);
From there, the group by becomes much simpler as well. When using LINQ to group by multiple fields, you'll want an IEqualityComparer class that looks like this:
public class AccountComparer : IEqualityComparer<IAccount>
{
public bool Equals(IAccount x, IAccount y)
{
return x.AccountCode == y.AccountCode &&
x.AccountDate == y.AccountDate &&
x.SalesCode == y.SalesCode;
}
public int GetHashCode(IAccount obj)
{
return (obj.AccountCode + obj.AccountDate + obj.SalesCode).GetHashCode();
}
}
The group by call is then one line:
var groups = allList.GroupBy(a => a, new AccountComparer());
Alternatively, you could create an AccountKey class that only has the 3 key fields and return that from the keySelector lambda. That feels a little cleaner, but is a bit more code.
From there, you can select the sums like this:
var amountSums = from g in groups
select new
{
g.Key.AccountCode,
g.Key.AccountDate,
g.Key.SalesCode,
Amount = g.Sum(a => a.Amount)
};

How to implement Entity Framework Code First join

I'm starting to use Entity Framework Code First.
Suppose to have such POCO's (ultimately simplified):
public class BortStructure
{
public Guid Id { get; set; }
public String Name { get; set; }
}
public class Slot
{
public Guid Id { get; set; }
public String Name { get; set; }
public BortStructure { get; set; }
}
public class SystemType
{
public Guid Id { get; set; }
public String Name {get; set; }
}
public class SlotSystemType
{
public Guid Id { get; set; }
public Slot Slot { get; set; }
public SystemType SystemType {get; set; }
}
and a context
public MyContext : DbContext
{
public DbSet<BortStructure> BortStructures { get; set; }
public DbSet<Slot> Slots{ get; set; }
public DbSet<SystemType> SystemTypes { get; set; }
public DbSet<SlotSystemType> SlotSystemTypes { get; set; }
}
I have a task to get BortStructure by Id with list of attached Slots, each one with list of systemTypes attached.
Using SQL allowed me to do that with some JOIN's:
SELECT BortStructures.Id, BortStructures.Name, Slots.Id,
Slots.Name, SystemType.Id, SystemType.Name FROM
((BortStructures LEFT JOIN Slots ON BortStructures.Id = Slots.BortStructureId)
LEFT JOIN SlotSystemTypes ON SlotSystemTypes.SlotId = Slots.Id)
LEFT JOIN SystemTypes ON SystemTypes.Id = SlotSystemTypes.SystemTypeId
WHERE BortStructures.Id='XXXXXX' ORDER BY Slots.Id, SystemType.Id
But with Entity Framework Code First I don't have any idea howto do that.
If I use
var slotSystemTypes = from sl in MyContext.SlotSystemTypes
where sl.Slot.BortStructure.Id = XXXXXX
orderby sl.Slot.Id, sl.SystemType.Id
select sl;
i, of course, will receive nothing if BortStructure consists of no Slots/Slots without any SystemTypes attached.
Instead of getting BortStructure with empty list of Slots/with Slots, each one with empty list of SystemTypes attached as I expect to get.
Is there any way to archive that with single LINQ query for my database configuration?
You can use join operator example:
string[] categories = new string[]{
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood" };
List<Product> products = GetProductList();
var q =
from c in categories
join p in products on c equals p.Category
select new { Category = c, p.ProductName };
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
more samples in: http://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9

Linq Find Item by Primary Key and display Name(that lives in a different table)

I am displaying a record from my database. The record pulls data from other tables and uses a Int in the main table to represent the value so Item table has a Division equal to 1 and the Division table 1 = ALL . Now that i am displaying the records i am trying to turn the 1 into all. All the ID fields show the int. Which is what my code is telling it to do. But i am trying to display the name and when i do that i get a lot of red. It cannot find the name. CatagoryID should be CategoryName.
Hope that makes sense.
if (!IsPostBack)
{
string v = Request.QueryString["ContactID"];
int itemid;
int.TryParse(v, out itemid);
var customerInfo = GetCustomerInfo(itemid);
CONTACTID.Text = customerInfo[0].ContactID.ToString();
ContactTitle.Text = customerInfo[0].ContactTitlesID.ToString();
ContactNameB.Text = customerInfo[0].ContactName;
DropDownAddCategory.Text = customerInfo[0].CategoryID.ToString();
DDLAddDivision.Text = customerInfo[0].DivisionID.ToString();
ContactPhoneBox.Text = customerInfo[0].ContactOPhone;
ContactCellBox.Text = customerInfo[0].ContactCell;
ContactEmailBox.Text = customerInfo[0].ContactEmail;
CextB.Text = customerInfo[0].Ext;
}
private List<Solutions.Models.Contact> GetCustomerInfo(int itemid)
{
using (ItemContext context = new ItemContext())
{
return (from c in context.Contacts
where c.ContactID == itemid
select c).ToList();
}
}
This is the model
public class Contact
{
[ScaffoldColumn(false)]
public int ContactID { get; set; }
public System.DateTime ContactCreated { get; set; }
public string ContactName { get; set; }
public int? ContactTitlesID { get; set; }
public string ContactOPhone { get; set; }
public bool cApproved { get; set; }
public string User { get; set; }
public string ContactCell { get; set; }
public string ContactEmail { get; set; }
public int? DivisionID { get; set; }
public int? CategoryID { get; set; }
[StringLength(5)]
public string CExt { get; set; }
public virtual Division Division { get; set; }
public virtual Category Category { get; set; }
public virtual ContactTitle ContactTitle { get; set; }
public string Ext { get; set; }
}
With Entity Framework you can include related entities in query results:
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).ToList();
This will return contacts with Catagory objects: customerInfo.Catagory.CategoryName
BTW instead of returning list of contacts and selecting first one by index (thus possibly having index out of range exception), modify your method to return first contact (or default, if not found):
private Solutions.Models.Contact GetCustomerInfo(int itemid)
{
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).FirstOrDefault();
}
And use it this way:
var customerInfo = GetCustomerInfo(itemid);
if (customerInfo != null)
{
CONTACTID.Text = customerInfo.ContactID.ToString();
// etc
}
Are you using LINQ to SQL or Entity Framework? Check your model again and make sure the relationship between the two tables are setup correctly. The relationship may be missing from the model, and causing this problem.

Categories