Is it possible to combine these 2 linq queries? - c#

I'm using entity framework to return my data.
I have 2 almost identical methods/queries. The only difference is that one has an additional Where statement.
The first query gets the average value of accepted transactions, and the second query gets the average of all transactions.
Here are the 2 methods:
static IEnumerable<BuyerEarning> GetBuyerEPA()
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
return r.Find()
.Where(x =>
x.ResultTypeId == (int)MatchResultType.Accepted &&
x.CreatedOn <= collectTo &&
x.CreatedOn >= collectFrom)
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.ToList()
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission)))
.ToList();
}
}
static IEnumerable<BuyerEarning> GetBuyerEPL()
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
return r.Find()
.Where(x =>
x.CreatedOn <= collectTo &&
x.CreatedOn >= collectFrom)
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.ToList()
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission)))
.ToList();
}
}
Rather than have 2 distinct queries I'd like to use 1 that returns BuyerId, TreeId, TierId, EpaValue, EplValue. Is this possible and if so, how?

While most of the other answers are showing you how to use the same code to toggle the query between EPL and EPA, I believe what you are asking is how to get both values in a single query.
static IEnumerable<BuyerEarning> GetBuyerEPA()
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
return r.Find()
.Where(x =>
x.CreatedOn <= collectTo &&
x.CreatedOn >= collectFrom)
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission), //EPL
x.Where(y => y.ResultTypeId == (int)MatchResultType.Accepted)
.Average(y => y.Commission)) //EPA
.ToList();
}
}

You can create helper method with a predicate as parameter:
static IEnumerable<BuyerEarning> GetXXXX(Func<MatchHistory, bool> predicate = null)
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
var filtered = r.Find()
.Where(x.CreatedOn <= collectTo && x.CreatedOn >= collectFrom);
if(predicate != null)
filtered = filtered.Where(predicate);
return filtered
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.ToList()
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission)))
.ToList();
}
}
And then use it within each of the original methods:
static IEnumerable<BuyerEarning> GetBuyerEPA()
{
return GetXXXX(x => x.ResultTypeId == (int)MatchResultType.Accepted);
}
static IEnumerable<BuyerEarning> GetBuyerEPL()
{
return GetXXXX();
}
Btw., why do you use ToList() before final projection? It makes the Average calculation being performed by application (using LINQ to Objects), which is much less efficient then when done by SQL Server.

Why not refactor the Where clause and pass it in as a variable?
var epaFilter = new Func<MatchHistory, bool>(x => x.ResultTypeId == (int)MatchResultType.Accepted && x.CreatedOn <= collectTo && x.CreatedOn >= collectFrom);
var eplFilter = new Func<MatchHistory, bool>(x => x.CreatedOn <= collectTo && x.CreatedOn >= collectFrom);
private static IEnumerable<MatchHistory> GetBuyerByFilter(Func<MatchHistory,Boolean> filter)
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
return r.Find()
.Where(filter)
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.ToList()
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission)))
.ToList();
}
}

Seems like you want this:
static IEnumerable<BuyerEarning> GetBuyer(bool acceptedOnly)
{
var collectTo = DateTime.Now.AddDays(-1).DayEnd();
var collectFrom = collectTo.AddDays(-29).DayStart();
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
IQueryable<MatchHistory> results = r.Find()
.Where(x =>
x.CreatedOn <= collectTo &&
x.CreatedOn >= collectFrom);
if (acceptedOnly)
{
results = results
.Where(x => x.ResultTypeId == (int)MatchResultType.Accepted);
}
return results
.GroupBy(x => new
{
x.BuyerId,
x.TreeId,
x.TierId
})
.ToList()
.Select(x => new BuyerEarning(
x.Key.BuyerId,
x.Key.TreeId,
x.Key.TierId,
x.Average(y => y.Commission)))
.ToList();
}
}

Related

Duplicated linq query

I have 2 almost identical linq queries and want to remove repeating code from it. The only difference is the extra property in the GroupBy depending on some true/false condition.
How can I conditionally group by in linq without repeating the code like below?
var allergensList = _context.RecipeAllergens
.Where(x => x.ParentId == Id && x.AllergenId != null)
.ToList();
var allergens = new List<AllergenInfo>();
if (isRecipe)
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, x.AllergenMaycontains })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
else
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
You can left grouping by x.AllergenMaycontains but under condition
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, AllergenMaycontains = isRecipe ? x.AllergenMaycontains : false })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();

Linq-to-SQL query (AsEnumerable) on multiple tables

Here's a Linq-to-SQL query that uses only one table from my SQL Server database and works perfectly:
private void GetData()
{
DateTime d = DateTime.Now;
using (DataClasses1DataContext dc = new DataClasses1DataContext())
{
var qte = dc.ENTREES_STOCKS.AsEnumerable()
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
.Select(s => new
{
art = s.ART_CODE,
date = s.ENTSTK_DTENTREE,
numLot = s.ENTSTK_LOT,
pnet = s.ENTSTK_PNET,
nbu = s.ENTSTK_NBU
})
.GroupBy(g => new { g.art, g.date, g.numLot })
.Select(n => new
{
n.Key.art,
n.Key.date,
n.Key.numLot,
pnet = n.Sum(x => Math.Round(Convert.ToDecimal(x.pnet), 2)),
nbu = n.Sum(x => Math.Round(Convert.ToDecimal(x.nbu), 2)),
});
QEntreeTB.Text = qte.First().pnet.ToString();
NbuEntreeTB.Text = qte.First().nbu.ToString();
}
}
How could I modify this code to join other tables to this query like :
private void GetData()
{
DateTime d = DateTime.Now;
using (DataClasses1DataContext dc = new DataClasses1DataContext())
{
var qte = dc.ENTREES_STOCKS.AsEnumerable()
// Thoseline of codes of course doesn't work
join art in dc.FICHES_ARTICLES on ENTREES_STOCKS.ART_CODE equals art.ART_CODE
join ent in dc.STK_ENT on art.ART_CODE equals ent.ART_CODE
....
//
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
.Select(s =>
new
{
art = s.ART_CODE,
date = s.ENTSTK_DTENTREE,
numLot = s.ENTSTK_LOT,
pnet = s.ENTSTK_PNET,
nbu = s.ENTSTK_NBU
}
)
.GroupBy(g => new { g.art, g.date, g.numLot })
.Select(n =>
new
{
n.Key.art,
n.Key.date,
n.Key.numLot,
pnet = n.Sum(x => Math.Round(Convert.ToDecimal(x.pnet), 2)),
nbu = n.Sum(x => Math.Round(Convert.ToDecimal(x.nbu), 2)),
}
);
QEntreeTB.Text = qte.First().pnet.ToString();
NbuEntreeTB.Text = qte.First().nbu.ToString();
}
}
Or is there à way to code this query another way ??
Because in fact i just want to join multiples tables, groupby some fields and sum others fields.
Firstly, calling AsEnumerable is a bit redundent. Then you can simply use the Join extension method.
var qte = dc.ENTREES_STOCKS
.JOIN(dc.FICHES_ARTICLES,art=>art.ART_CODE, stock => stock.ART_CODE)
.JOIN(dc.STK_ENT,ent => ent.ART_CODE,stock => stock.ART_CODE)
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
....
You can find more answers here:
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/method-based-query-syntax-examples-join-operators

Linq to find lasted record in group

I want add a new column to find which is lasted record in group.
Can I write subquery in Select() method?
I have try this
var test = DailyPeriods.Where(x => x.BookingDate == "2016/12/30")
.Select(x =>
new
{
PERIOD_GROUP_ID = x.PeriodGroupID,
PERIOD_NAME = x.PeriodName,
New_Column = DailyPeriods
.Where(z => z.BookingDate == "2016/12/30")
.Select(a =>
new
{
PeriodGroupID = a.PeriodGroupID,
period_name = a.PeriodName
}
)
.GroupBy(b => b.period_name)
.Select(g => g.Last().PeriodGroupID)
.Contains(x.PeriodName)
})
But will occur this error
"column not in scope: A:2211708.C(BOOKING_DATE)"
Try this..
var lastRecords = periodList.GroupBy(l => l.PeriodName)
.Select(x => new { PeriodName = x.Key,
PeriodGroupId = x.OrderBy(l => l.PeriodGroupId).Last().PeriodGroupId});
var result = from period in periodList
from lastRec in lastRecords.Where(r => r.PeriodGroupId == period.PeriodGroupId
&& r.PeriodName == period.PeriodName)
.DefaultIfEmpty()
select new { period.PeriodGroupId,period.PeriodName, New_Column=lastRec==null?false:true };

Linq GroupBy With Passed Where Predicate

This works fine.g.Key is not null and has appropriate data:
var result = db.JournalEntries.Include(je => je.JournalRecords.Select(jr => jr.Account).Select(j => j.AccountParticulars))
.Where(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To)
.SelectMany(s => s.JournalRecords)
.GroupBy(d => d.AccountParticular.Account.AccountCategory)
.Select(g => new { Name = g.Key.Name });
But this does not work as g.Key is null:
var DateFilter = new Func<JournalEntry, bool>(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To);
var result = db.JournalEntries.Include(je => je.JournalRecords.Select(jr => jr.Account).Select(j => j.AccountParticulars))
.Where(DateFilter)
.SelectMany(s => s.JournalRecords)
.GroupBy(d => d.AccountParticular.Account.AccountCategory)
.Select(g => new { Name = g.Key.Name });
I tried the same thing in a simple console app with static collection and passing in predicate works fine. What could be the problem here?
NOTE: Lazy loading/dynamic proxy is disabled
Try
var DateFilter = new Expression<Func<JournalEntry, bool>>(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To);
as you need to pass an expression tree to EF

OrderByDescending doesn't work in nested linq statement

In Linqpad, i can see the correct list. But in code, after putting in the list collection, order by doesn't work for BeginDate. If i use BeginDate with Max, it works. I don't understand where i am wrong?
var templist = contentRepository
.Get(q => (q.Status == (int)StatusEnum.Active) &&
(q.CategoryId == category.GetHashCode() || q.Category.ParentId == category.GetHashCode())
&& q.MinorVersion == 0
&& q.MajorVersion > 0)
.GroupBy(q => q.VersionId)
.OrderByDescending(q => q.Key)
.Select(q => new
{
VersionId = q.Key,
Id = q.Max(x => x.Id),
MajorVersion = q.Max(x => x.MajorVersion),
UpdatedAt = q.Max(x => x.UpdatedAt),
//BeginDate = q.Max(x=>x.BeginDate),
BeginDate = (q.OrderByDescending(x => x.Id).Take(1).Select(x=>x.BeginDate)).First(),
Title = (q.OrderByDescending(x => x.Id).Take(1).Select(x => x.Title)).First(),
ShowOnHomePage = (q.OrderByDescending(x => x.Id).Take(1).Select(x=>x.ShowOnHomePage)).First()
})
.OrderByDescending(x => x.BeginDate)
.Take(maxItemCount)
.ToList();
List<ContentEntity> contents = new List<ContentEntity>();
templist.ForEach(q => contents.Add(
contentRepository
.Get(x => x.VersionId == q.VersionId && x.MajorVersion == q.MajorVersion && x.MinorVersion == 0)
.FirstOrDefault()
));
return contents.Where(q => q.ShowOnHomePage == true)
.OrderByDescending(q => q.MajorVersion)
.OrderByDescending(q => q.BeginDate)
.Take(maxItemCount)
.ToList();
You are ordering by Id, not by BeginDate. Equivalent code for
q.Max(x => x.BeginDate)
Will be
q.OrderByDescending(x => x.BeginDate).Take(1).Select(x => x.BeginDate).First()
Or simplified
q.OrderByDescending(x => x.BeginDate).First().BeginDate

Categories

Resources