How to group multiple lists using LINQ - Sum and GroupBy - c#

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)
};

Related

LINQ - Group by with SUM IF

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

I dont know what im doing wrong with my lambda expression, can someone correct me?

I'm using FrameworkEntity and have two classes:
public class InfoComplementarEmpresaModel
{
public int idempresa { get; set; }
[Key]
public string idcomplemento { get; set; }
public string idinformacao { get; set; }
public string conteudocomplemento { get; set; }
public string periodocomplemento { get; set; }
}
and
public class InfoComplementarModel
{
public int idlayout { get; set; }
[Key]
public int idinformacao { get; set; }
public string codigoinformacao { get; set; }
public string descricaoinformacao { get; set; }
public int tipoinformacao { get; set; }
}
The lambda join im trying to do is the following:
public List<string> GetTipoENomeDeInformacaoComplementarEmpresa(string idDaEmpresa)
{
List<string> listaDeTipos = new List<string>();
int idDaEmpresaNoFormatocerto = Convert.ToInt32(idDaEmpresa);
listaDeTipos.Add("Criar novo preenchimento de valores");
var nomesDeInformacoes = db.InformacoesComplementaresDaEmpresa
.Where(a => a.idempresa == idDaEmpresaNoFormatocerto)
.Join(db.InformacoesComplementaresDoLayout,
infocompempresa => new {infocompempresa.idinformacao},
infocomplayout => new {infocomplayout.idinformacao},
(ice, icl) => ice.idinformacao)
.ToList();
// some method that will put nomesDeInformacoes in listaDeTipos
return listaDeTipos;
}
The Join accuses "The type arguments for method Queryable.Join(IQueryable, IQueryable, Expression>, Expression>, Expression>) cannot be inferred from the usage. Try specifying the type arguments explicitly"
I FIGURED IT OUT
there were various problems:
idinformacao in InfoComplementarEmpresaModel was a string instead of a int
Then i discovered that ALL the anonymous names in the join must remain the same, resulting in:
var nomesDeInformacoes = db.InformacoesComplementaresDaEmpresa.Where(a => a.idempresa == idDaEmpresaNoFormatocerto)
.Join(db.InformacoesComplementaresDoLayout,
ice => ice.idinformacao ,
icl => icl.idinformacao ,
(ice, icl) => new { ice, icl } )
.Select(
//creating a class to accept the fields i want
})
.ToList();
This guy works, returning in the class im creating.

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.

Linq query results to List collection

I use the code below to get records from two tables but I want to have the result in a List. I used the code below, but this error occured:
Cannot implicitly convert type
System.Collections.Generic.List<AnonymousType#1> to
System.Collections.Generic.List<QuickRoutes.DAL.Route>
public List<Route> GetAllForUser(Guid userId)
{
// var result = new List<Route>();
aspnetdbDataContext aspdb = new aspnetdbDataContext();
List<Route> result = (from r in aspdb.Routes
where r.UserId == userId
join t in aspdb.TrackPoints on r.RouteId equals t.RouteFK
select t.TrackPointId,t.RouteFK,t.TrackTime,t.Longitude,t.Latitude,t.Elevation, r.SourceName).ToList();
return result;
}
EDIT:
I want to match my query with this class:
namespace QuickRoutes.Entities
{
public enum RouteType
{
Route, Waypoints, Track
}
public sealed class Route
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Time { get; set; }
public List<TrackPoint> TrackPoints { get; set; }
public Guid UserId { get; set; }
public string GpxData { get; set; }
public RouteType RouteType { get; set; }
public Route(int id)
{
Id = id;
TrackPoints = new List<TrackPoint>();
}
......
but if I change my code to this one,It doesn't work too
List<Route> result = (from r in aspdb.RouteLinqs
where r.UserId == userId
join t in aspdb.TrackPointlinqs on r.RouteId equals t.RouteFK
select r).ToList();
to be honest I want to change this function that is written with SQL to LINQ,have any better idee?
namespace QuickRoutes.Entities
{
public enum RouteType
{
Route, Waypoints, Track
}
public sealed class Route
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Time { get; set; }
public List<TrackPoint> TrackPoints { get; set; }
public Guid UserId { get; set; }
public string GpxData { get; set; }
public RouteType RouteType { get; set; }
public Route(int id)
{
Id = id;
TrackPoints = new List<TrackPoint>();
}
......
It's because your projection (what you're selecting) isn't the same as a Route (what you're trying to return);
If you wish to return a Route Change your select to:
select r
If you wish to return your joined projection, you'l have to create a new type, and return that:
e.g:
public class RouteTrack
{
public TrackPointId ...
public RouteFK ...
public TrackTime ...
}
And return a List<RouteTrack> from your method, and change your select to:
select new RouteTrack { TrackPointId = t.TrackPointId .... }
Since your query selects properties from two different tables, it obviously no longer returns objects of type Route, but a new type of object (an anonymous type).
If you want to return this new type, you need to define it explicitly, then return the data using this type.
public class MyClass
{
public MyClass(int trackPointId, DateTime trackTime, ...)
{
TrackPointId = trackPointId;
TrackTime = trackTime;
...
}
public int TrackPointId { get; set; }
public DateTime TrackTime { get; set }
...
}
List<MyClass> result = (from r in aspdb.Routes
where r.UserId == userId
join t in aspdb.TrackPoints on r.RouteId equals t.RouteFK
select new MyClass(t.TrackPointId,t.RouteFK,t.TrackTime,t.Longitude,t.Latitude,t.Elevation, r.SourceName);
I have faced the same. The issue here is that you are creating an anonymous type that contains fields from both the Route and aspdb.TrackPoints.
Hence you cannot cast this to List.
This is a mapping problem.
To resolve this you need to create a newcustom class that contains all the fields/properties that you are getting out from the query.
Let's say you create a custom class say RouteMapper then map using code like
select new RouterMapper
{
RouteMapper.TrackPointId = t.TrackPointId, RouteMapper.RouteFK=t.RouteFK, RouteMapper.SourceName = r.SourceName
}
This way now you return List< RouteMapper>.

Contains with Linq

I have class "Estimation", this class has a property "EstimationItems" (the type is IList)
public class EstimationItem
{
public virtual int Id { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal Price { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
public virtual string Name { get; set; }
}
When I have an instance of "Estimation", I'd like to know if "EstimationItems" contain a product with the code "MyCode".
Using this :
List<EstimationItem> items = new List<EstimationItem>();
// Add items
int searchedCode = 1
if(items.Any(i => i.Product.Code == searchedCode))
{
// Contained
}
You can use Any():
bool hasMyCode = yourEstimation.EstimationItems.Any(
item => item.Product.Code == "MyCode");
Enumerable.Any Method determines whether any element of a sequence satisfies a condition.
Boolean result = estimationItems.Any(x => x.Product.Code == "MyCode");
var query = from ei in EstimationItems where ei.Product.Code == "MyCode" select ei;

Categories