Simple query performance - c#

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.

Related

LiteDb auto-increment id sub list of class

I have these classes and got issue with auto increase id of sub lists:
class A
{
public int Id { get; set; }
public string Value { get; set; }
public List<B> Bs { get; set; }
}
class B
{
public int Id { get; set; }
public string Value { get; set; }
public List<C> Cs { get; set; }
}
class C
{
public int Id { get; set; }
public string Value { get; set; }
}
How can I automatically increase the id of the B and C? I can add values but id inside the sub list they're always 0.
In general, an A item can contain B with the same id, as well as different B items can contain C with the same id.
If you just need different ids, you can assign them right in the code just like other properties.
If it is required to support consistency and assign a new id to every sub-item, the items should be stored in separate collections:
var collectionA = db.GetCollection<A>("a");
var collectionB = db.GetCollection<B>("b");
var collectionC = db.GetCollection<C>("c");
BsonMapper.Global.Entity<A>().DbRef(x => x.Bs, "b");
BsonMapper.Global.Entity<B>().DbRef(x => x.Cs, "c");
foreach (var c in a.Bs.SelectMany(x => x.Cs).Where(x => x.Id == 0))
collectionC.Insert(c);
foreach (var b in a.Bs.Where(x => x.Id == 0))
collectionB.Insert(b);
collectionA.Insert(a);
More details about sub-documents are available in the documentation

How to group by a list of objects and declare this list as an attribute of a viewmodel

Basically I have this class
public class Gasto
{
public int IdTienda { get; set; }
public int IdGasto { get; set; }
public System.DateTime Fecha { get; set; }
public string ConceptoDeGasto { get; set; }
public double Total { get; set; }
public string TipoDeGasto { get; set; }
public Nullable<int> IdVenta { get; set; }
public Nullable<System.DateTime> FechaVenta { get; set; }
public virtual Tienda Tienda { get; set; }
}
And I'm trying to build a ViewModelClass like this
public class CorteConVentas
{
// STILL NO ATRIBUTE -- THIS IS THE QUESTION
}
Here is the code for the controller where I will build a List of Gasto grouped by TipoDeGasto
var gastos = db.Gastos.Where(g => g.IdGasto >= corte.DesdeIdGasto && g.IdGasto <= corte.HastaIdGasto).ToList();
var GD = gastos.GroupBy(u => u.TipoDeGasto).Select(grp => new { TipoGasto = grp.Key, gastos = grp.ToList() } ).ToList();
As you can see the variable "GD" is a List of Strings (TipoGasto) with List of Gasto.
¿The issue (question) is this GD how can I declare it as an attribute of my viewModelClass?
I tried something like this for the ViewModel
public class CorteConVentas
{
public List<string, List<Gasto>> listaGastosAgrupada { get; set; }
}
But there is something wrong. The output of the error says:
Using the generic type List requires 1 type arguments
Here is the output after grouping by
Finally the solution as #Ziv Weissman said was not to use an anonymous type
So I created a class like this
public class CorteConVentas
{
public List<GastosAgrupados> listaGastosAgrupada { get; set; }
}
public class GastosAgrupados
{
public string TipoGasto { get; set; }
public List<Gasto> gastos { get; set;}
}
And then in the controller when creating the grouped list I did this
var gastos = db.Gastos.Where(g => g.IdGasto >= corte.DesdeIdGasto && g.IdGasto <= corte.HastaIdGasto).ToList();
var gd = gastos.GroupBy(u => u.TipoDeGasto).Select(grp => new GastosAgrupados { TipoGasto = grp.Key, gastos = grp.ToList()) } ).ToList();
Thanks to all for helping me.
You cannot declare a variable of anonymous type:
.Select(grp => new { TipoGasto = grp.Key, gastos = grp.ToList() } )
You must create another class which has these two props.
(or use a KeyValuePair)
Something like -
.Select(grp => new KeyValuePair<string,List<Gasto>> { Key = grp.Key, Value = grp.ToList() } )
Then you can create a strong typed prop.
public class CorteConVentas
{
List<KeyValuePair<string,List<Gasto>>> PropName {get; set;}
}
Simple answer, just look up GroupBy() in the docs, and see what it returns:
IEnumerable<IGrouping<TKey, TElement>>
which is your case would be:
public class CorteConVentas
{
public IEnumerable<IGrouping<string, Gasto>> listaGastosAgrupada { get; set; }
}
Update:
Didn't notice the select after the GroupBy(). Try this:
public class GrupoGastos // forgive my attempts at Spanish via Google Translate
{
public string TipoGasto {get; set;}
public List<Gasto> Gastos {get; set;}
}
then
var GD = gastos.GroupBy(u => u.TipoDeGasto)
.Select(grp => new GrupoGastos
{ TipoGasto = grp.Key, Gastos = grp.ToList() } )
.ToList();
and finally:
public class CorteConVentas
{
public List<GrupoGastos> listaGastosAgrupada { get; set; }
}
First, looks like you want a dictionary, not a list:
public class CorteConVentas
{
public Dictionary<string, List<Gasto>> dictaGastosAgrupada { get; set; }
}
Second, that select list with two columns is going to give you trouble because it comes out as an anonymous type, and you have no way to declare a dictionary that will contain it. Instead, just return the object:
var GD = gastos.GroupBy(u => u.TipoDeGasto).Select(grp);
So now you have GD which will let you enumerate over a list of grp objects. You can add them to your dictionary like this:
foreach (var grp in GD) dictaGastosAgrupada.Add(grp.Key, grp.ToList());

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

Entity Framework - customize navigation properties to abstract joins

I have looked through similar questions here, but not seen this specific scenario.
Using EF 6 Code First, I have three tables, A, B and C. The relationship is A => B = 1:M, and B=>C = 1:1
The end result in this schema is that there is an implicit 1:M between A and C.
I do NOT want the consumer of the Entity Framework model to know about B. Ideally they would have a 1:M Navigation property from A to C (and I'd like to be able to surface this Entity Model through Web API and OData as IQueryable)
How could I do this?
If I add a custom [NotMapped] property to A which a collection of C, I have no way of populating C within the getter of that property because the entity doesn't know about its context.
Anyone have any ideas as to how to implement an IQueryable where A has a navigation property to C and B is 'abstracted' out of existence?
EDIT
Attempted to put the following into the code first entity A:
[NotMapped]
public ICollection<C> Cs
{
get { return this.Bs.Select(b => b.C) as ICollection<C>; }
}
But got this error:
The navigation property 'C' is not a declared property on type 'A'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Thanks.
Here is an example.
public static class OrderDAL
{
public static Order Get(int key)
{
using (var context = new AppContext())
{
var order = context.Orders.Include(a => a.OrderDetails.Select(b => b.Information)).FirstOrDefault(a => a.Id == key);
// Fills C.
order.OrderDetailAdditionalInformation = order.OrderDetails.Select(b => b.Information).ToArray();
// Hides information about B.
foreach (var information in order.OrderDetailAdditionalInformation)
{ information.OrderDetail = null; }
order.OrderDetails = null;
return order;
}
}
}
public class AppContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}
// A
public class Order
{
public int Id { get; set; }
public DateTime Date { get; set; }
public ICollection<OrderDetail> OrderDetails { get; set; }
[NotMapped]
public ICollection<OrderDetailAdditionalInformation> OrderDetailAdditionalInformation { get; set; }
}
// B, one A many B
public class OrderDetail
{
public int Id { get; set; }
public int Qty { get; set; }
public string Item { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public OrderDetailAdditionalInformation Information { get; set; }
}
// C, one B one C
public class OrderDetailAdditionalInformation
{
[ForeignKey("OrderDetail")]
public int Id { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Long { get; set; }
public OrderDetail OrderDetail { get; set; }
}

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