Convert the following sql query to lambda expression - sql

How to convert the following sql query to lambda expression?
select cg.Code, ci.ChangeType, SUM(oc.Value) from OtherCharges oc
left join changeitems ci on oc.ChangeItemKey = ci.ChangeItemKey
left join ChangeGroups cg on ci.ChangeGroupKey = cg.ChangeGroupKey
where OtherKey = 'AB235A00-FEB2-4C4F-B0F9-3239FD127A8F'
group by cg.Code, ci.ChangeType
order by cg.Code, ci.ChangeType

Assuming you already have .NET domain types for your tables:
IQueryable<OtherCharges> otherCharges = ...
Guid otherKey = ...
var query = otherCharges.Where(oc => oc.OtherKey == otherKey)
.Select(oc => new { oc.ChangeItem, oc.Value })
.GroupBy(t => new { t.ChangeItem.ChangeGroup.Code, t.ChangeItem.ChangeType })
.OrderBy(g => g.Key.Code)
.ThenBy(g => g.Key.ChangeType)
// note that if Code is a non-nullable type you'll want to cast it to int? at some
// point so that when pulled into memory EF won't complain that you can't cast
// null to a non-nullable type. I expect that Code could sometimes be null here
// do to your use of LEFT OUTER JOIN in the T-SQL
.Select(g => new { g.Key.Code, g.Key.ChangeType, Sum = g.Sum(t => t.Value) });
var inMemoryResult = query.ToList();
Note that I'm using OtherCharge.ChangeItem and ChangeItem.ChangeGroup here. These are association properties and need to be set up as part of your model (e. g. using fluent configuration for EF code first).

Related

Linq2DB can't translate a mapped column in Where clause

I'm working with a legacy Oracle database that has a column on a table which stores boolean values as 'Y' or 'N' characters.
I have mapped/converted this column out like so:
MappingSchema.Default.SetConverter<char, bool>(ConvertToBoolean);
MappingSchema.Default.SetConverter<bool, char>(ConvertToChar);
ConvertToBoolean & ConvertToChar are simply functions that map between the types.
Here's the field:
private char hasDog;
[Column("HAS_DOG")]
public bool HasDog
{
get => ConvertToBoolean(hasDog);
set => hasDog = ConvertToChar(value);
}
This has worked well for simply retrieving data, however, it seems the translation of the following:
var humanQuery = (from human in database.Humans
join vetVisit in database.VetVisits on human.Identifier equals vetVisit.Identifier
select new HumanModel(
human.Identifier
human.Name,
human.HasDog,
vetVisit.Date,
vetVisit.Year,
vetVisit.PaymentDue
));
// humanQuery is filtered by year here
var query = from vetVisits in database.VetVisits
select new VetPaymentModel(
(humanQuery).First().Year,
(humanQuery).Where(q => q.HasDog).Sum(q => q.PaymentDue), -- These 2 lines aren't correctly translated to Y/N
(humanQuery).Where(q => !q.HasDog).Sum(q => q.PaymentDue)
);
As pointed out above, the .Where clause here doesn't translate the boolean comparison of HasDog being true/false to the relevant Y/N values, but instead a 0/1 and results in the error
ORA-01722: invalid number
Is there any way to handle this case? I'd like the generated SQL to check that HAS_DOG = 'Y' for instance with the specified Where clause :)
Notes
I'm not using EntityFramework here, the application module that this query exists in doesn't use EF/EFCore
You can define new mapping schema for your particular DataConnection:
var ms = new MappingSchema();
builder = ms.GetFluentMappingBuilder();
builder.Entity<Human>()
.Property(e => e.HasDog)
.HasConversion(v => v ? 'Y' : 'N', p => p == 'Y');
Create this schema ONCE and use when creating DataConnection

NHibernate Linq Expression dynamic projection

How can i dynamically change the selected columns in the generated sql query when using a linq expression?
Its a new session for each time the query is executed.
Even when I set the MapExp as null after first creation an then changing the bool value to false, it still generates the column in the sql query.
The code runs in a wpf application.
System.Linq.Expressions.Expression<Func<Entity, Model>> MapExp = x => new Model
{
Id=xId,
Count= LoadFormulaField ? x.Count: null,
...
};
var result = session.Query<Entity>().Select(MapExp))
Your problem seems to be the ternary-conditional as part of the expression which is causing the "Count" column to always be queried.
One option to avoid this could be:
var query = session.Query<Entity>();
IQueryable<Model> result = null;
if (LoadFormulaField)
{
result = query.Select(x => new Model
{
Id = x.Id,
Count = x.Count,
});
}
else
{
result = query.Select(x => new Model
{
Id = x.Id,
});
}
Which would get a little less ugly if you separate in a couple of methods I think.

linq results for display in view - How to apply grouping

How can I group the results of this query by wl.WishlistID?
var projected = (from wl in context.Wishlists.Where(x => x.UserId == 6013)
from wli in wl.WishlistItems
select new wishListProjection
{
wlId = wl.WishlistID,
wlUserId = (int)wl.UserId,
wlDesc = wl.description,
wlTitle = wl.Title,
wliWishlistItemID = wli.WishlistItemID,
wliQtyWanted = (int)wli.QtyWanted,
wliPriceWhenAdded = wli.PriceWhenAdded,
wliDateAdded = (DateTime)wli.DateAdded,
wliQtyBought = (int)wli.QtyBought,
}).ToList();
This returns the results I want, but I want to be able to iterate over them in the view without repeating the parent-level Wishlist object.
I've tried adding the line:
group wl by wl.WishlistID into g
But I don't seem to be able to access any properties by using g.[propertyname]
This grouping achieves more or less what I want, but I want to convert the results into a new or anonymous type, rather than return the entire object.
var results = context.WishlistItems.GroupBy(x => x.Wishlist).
Select(group => new { wl = group.Key, items = group.ToList() }).ToList();
You cannot access the properties of g because when you do the grouping:
group wl by wl.WishlistID into g
g is of type IGrouping<typeof(wl.WishlistID),typeof(wl)>, which is essentially a collection of all wl's with the same key wl.WishlistID. In other words, you cannot access the properties of g because g is not a single entity, it is a collection of those entities.
For your second grouping you said you would like to create an anonymous type instead of the entire object. You can do this by doing the selection first and then grouping:
var results = context.WishlistItems
.Select(x => new { })
.GroupBy(x => x.PropertyOfProjection)
.Select(group => new { wl = group.Key, items = group.ToList() }).ToList();
Or, using a nested sub query in your first example:
var projected = (from x in
(from wl in context.Wishlists.Where(x => x.UserId == 6013)
from wli in wl.WishlistItems
select new wishListProjection
{
wlId = wl.WishlistID,
wlUserId = (int)wl.UserId,
wlDesc = wl.description,
wlTitle = wl.Title,
wliWishlistItemID = wli.WishlistItemID,
wliQtyWanted = (int)wli.QtyWanted,
wliPriceWhenAdded = wli.PriceWhenAdded,
wliDateAdded = (DateTime)wli.DateAdded,
wliQtyBought = (int)wli.QtyBought,
})
group x by w.wlId into g).ToList();
I'm not sure what you mean by iterate without repeating the parent-level Wishlist object because whenever you create a grouping in Linq you will still have to have a nested foreach like:
foreach (var x in grouping)
{
x.Key;
foreach (var y in x)
{
y.Property;
}
}

Making thing more succint using linq to entities for inner joins

Any way to make this less verbose?
var model =
(
from MvrTable in
LinqEntitiesCtx.Mvrs
join MvrMedsTable in LinqEntitiesCtx.MvrMeds
.Where(Id => Id.FKMvrId == 1)//inner join will be fast with this!
on MvrTable.PKMvrId equals MvrMedsTable.FKMvrId
join MvrLocationTable in LinqEntitiesCtx.MvrLocations
on MvrTable.PKMvrId equals MvrLocationTable.FKMvrId
join MvrEmployeeTable in LinqEntitiesCtx.MvrEmployees
on MvrTable.PKMvrId equals MvrEmployeeTable.FKMvrId
//notice i am using a different primary key that previouslly
join MvrMedsAdminRouteTable in LinqEntitiesCtx.MvrMedsAdminRoutes
on MvrMedsTable.PKMvrMedsId equals MvrMedsAdminRouteTable.FKMvrMedsId
select new
{ //here I choose the columns I want to display
MvrTable.PKMvrId,
MvrTable.VarianceDescription,
MvrTable.CaseNumber,
MvrTable.DateOfReport,
MvrTable.DateOfVariance
}
);
Equivalent SQL code of above:
SELECT [t0].[PKMvrId], [t0].[VarianceDescription], [t0].[CaseNumber], [t0].[DateOfReport], [t0].[DateOfVariance], [t1].[PKMvrMedsId]
FROM [Mvrs] AS [t0]
INNER JOIN [MvrMeds] AS [t1] ON ([t0].[PKMvrId]) = [t1].[FKMvrId]
INNER JOIN [MvrLocations] AS [t2] ON ([t0].[PKMvrId]) = [t2].[FKMvrId]
INNER JOIN [MvrEmployees] AS [t3] ON [t0].[PKMvrId] = [t3].[FKMvrId]
INNER JOIN [MvrMedsAdminRoutes] AS [t4] ON ([t1].[PKMvrMedsId]) = [t4].[FKMvrMedsId]
WHERE [t1].[FKMvrId] =ParamMvrId
By using Associations it could probably be written more compact. Something like (not complete):
var model = from MvrTable in LinqEntitiesCtx.Mvrs
where MvrTable.MvrMeds.MvrLocations.Any() //These are the Associations
select new
{
MvrTable.PKMvrId,
MvrTable.VarianceDescription,
MvrTable.CaseNumber,
MvrTable.DateOfReport,
MvrTable.DateOfVariance
};
You don'y really need the joins since you are not getting any data from those tables. You should use Any instead which corresponds to SQL's EXISTS.
I believe changing the join's to from's will make it more clear. You could also abbreviate your entity alias's
var model =
(
from MvrTable in LinqEntitiesCtx.Mvrs
from MvrMedsTable in LinqEntitiesCtx.MvrMeds
.Where(Id => Id.FKMvrId == 1)
.Where(x => MvrTable.PKMvrId == x.FKMvrId)
from MvrLocationTable in LinqEntitiesCtx.MvrLocations
.Where(x => MvrTable.PKMvrId == x.FKMvrId)
from MvrEmployeeTable in LinqEntitiesCtx.MvrEmployees
.Where(x => MvrTable.PKMvrId == x.FKMvrId)
from MvrMedsAdminRouteTable in LinqEntitiesCtx.MvrMedsAdminRoutes
.Where(x => MvrMedsTable.PKMvrMedsId == x.FKMvrMedsId)
select new
{
MvrTable.PKMvrId,
MvrTable.VarianceDescription,
MvrTable.CaseNumber,
MvrTable.DateOfReport,
MvrTable.DateOfVariance
}
);

Avoid repeating an expression in a LINQ to Entities query

I have the following query:
val = val.Select(item => new SimpleBill { CTime = item.CTime, Description = item.Description, ID = item.ID,
IDAccount = item.IDAccount, OrderNumber = item.OrderNumber, PSM = item.PSM, Sum = item.Sum,
Type = (BillType)item.Type,
ByteStatus = ((Bill)item).Payments.OrderByDescending(payment => payment.ID).FirstOrDefault().Status,
LastPaymentDate = ((Bill)item).Payments.OrderByDescending(payment => payment.ID).FirstOrDefault().CTime,
LastPaymentSum = ((Bill)item).Payments.OrderByDescending(payment => payment.ID).FirstOrDefault().Sum });
}
Is it possible to avoid repeating the ((Bill)item).Payments.OrderByDescending(payment => payment.ID).FirstOrDefault() part 3 times? I tried turning it into a method and into a delegate - the code compiled in both cases, but produced an exception at runtime.
You can use the let contstruct as follows:
val = from item in val
let lastPayment = ((Bill)item).Payments
.OrderByDescending(payment => payment.ID)
.FirstOrDefault()
select new SimpleBill
{
lastPayment.CTime,
//Rest of fields
}
However, as you may noticed this uses the LINQ Query syntax vs. Method syntax. IIRC let is only available in the former.