Linq to Entities - Add a Second Join with Multiple ON conditions - entity-framework

I currently can do a successful join on two tables like this:
var query = db.DepoAccounts
.Join(db.DepoAccountDetails,
t1 => new { t1.INTEG_REF_NO, t1.BASE_DATE },
t2 => new { t2.INTEG_REF_NO, t2.BASE_DATE },
(t1, t2) => new DataCustomerAccountGDWHModel
{
BranchNumber = t1.BR_NO,
BookedBalance = t2.DP_FACE_BAL_VAL_ADJ,
}
);
I can join on a third table like this, in order to get AccountName:
var query = db.DepoAccounts
.Join(db.DepoAccountDetails,
t1 => new { t1.INTEG_REF_NO, t1.BASE_DATE },
t2 => new { t2.INTEG_REF_NO, t2.BASE_DATE },
(t1, t2) => new { t1, t2 })
.Join(db.AccountCodes,
j1 => j1.t2.DP_ACT_CD,
j2 => j2.ACT_CD,
(j1, j2) => new DataCustomerAccountGDWHModel
{
BranchNumber = j1.t1.BR_NO,
BookedBalance = j1.t2.DP_FACE_BAL_VAL_ADJ,
AccountName = j2.ACT_NAME
}
);
Now I want to add two more join conditions to what I just added, and I do this:
var query = db.DepoAccounts
.Join(db.DepoAccountDetails,
t1 => new { t1.INTEG_REF_NO, t1.BASE_DATE },
t2 => new { t2.INTEG_REF_NO, t2.BASE_DATE },
(t1, t2) => new { t1, t2 })
.Join(db.AccountCodes,
j1 => new { j1.t2.DP_ACT_CD, j1.t2.BASE_DATE, j1.t2.BR_NO },
j2 => new { j2.ACT_CD, j2.BASE_DATE, j2.BR_NO },
(j1, j2) => new DataCustomerAccountGDWHModel
{
BranchNumber = j1.t1.BR_NO,
BookedBalance = j1.t2.DP_FACE_BAL_VAL_ADJ,
AccountName = j2.ACT_NAME
}
)
However, this gives me the following compile error: The type arguments for method
'Queryable.Join (IQueryable). etc etc CANNOT BE INFERRED FROM THE USAGE.
Do I just have a syntax problem?

Related

Use FirstOrDefaultAsync in query when creating instance

I have the following Entity Framework query:
IQueryable<Unit> units = context.Units;
Product product = new Product {
Conversions = model.Conversions.Select(y => new Conversion {
UnitId = units
.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefault(),
Value = y.Value
}).ToList(),
Name = model.Name
}
I tried to use await before units.Where( ... as:
Product product = new Product {
Conversions = model.Conversions.Select(y => new Conversion {
UnitId = await units
.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefaultAsync(),
Value = y.Value
}).ToList(),
Name = model.Name
}
This is not allowed as it is inside new Conversion ...
Shouldn't I use await? How can I use it in this query?
You can use this approach :
unitId = await Task.Factory.Start<int>(()=>{
return units.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefault();
})
change int to your return type.

How to use lambda expression with joins

I am new to lambda exp. I have created a linq statement but I 'm not aware how to convert my code into lambda exp.
var result = (from order1 in Orders
join packedorder1 in VracExcluded on order1.codOrder equals packedorder1.codOrder
join product1 in Products on order1.codProduct equals product1.codProduct
join packedproduct1 in PackedProducts on packedorder1.codPackedProduct equals packedproduct1.codPackedProduct
where order1.codOrder == _order.codOrder
select new FinishedProductPrintingM
{
OF = order1.codOrder,
Item = "0",
Ligne = packedorder1.codLine,
Material = packedorder1.codPackedProduct,
Produit = product1.codProductType,
MaterialDescription = packedproduct1.lblPackedProduct,
Lot = packedorder1.codPackedBatch,
RéférenceClient = product1.codCustomerColor,
Quantité = packedorder1.nbrPackedQuantity.ToString(),
Déjàimprimé = packedorder1.nbrLabelPrinted
}).ToList();
Can anyone help me to understand, how can I create a lambda exp of my linq query?
Is is always useful to use msdn if you are not aware about how to use .net APIs.
var result = Orders.Join(VracExcluded, o => o.codOrder, v => v. codOrder, (o, v) => new { Order = o, VracExcluded = v })
.Join(Products, x => x.Order.codProduct, p => p.codProduct, (x, p) => new { Order = x.Order, VracExcluded = x.VracExcluded, Product = p })
.Join(PackedProducts, x => x.VracExcluded.codPackedProduct, pp => pp.codPackedProduct, (x, pp) => new { Order = x.Order, VracExcluded = x.VracExcluded, Product = x.Product, PackedProduct = pp })
.Where(x => x.Order.codOrder == _order.codOrder)
.Select(x => new FinishedProductPrintingM
{
OF = x.Order.codOrder,
Item = "0",
Ligne = x.VracExcluded.codLine,
Material = x.VracExcluded.codPackedProduct,
Produit = x.Product.codProductType,
MaterialDescription = x.PackedProduct.lblPackedProduct,
Lot = x.VracExcluded.codPackedBatch,
RéférenceClient = x.Product.codCustomerColor,
Quantité = x.VracExcluded.nbrPackedQuantity.ToString(),
Déjàimprimé = x.VracExcluded.nbrLabelPrinted
})
.ToList();

How can I achieve to implement the below query without the if statement (SUM/AVG)?

public class DailyAggregator : Aggregator
{
public override Dictionary<string, int?> Aggregate<T>(IQueryable<T> query, Expression<Func<T, DateTime>> groupByProperty, Expression<Func<T, double>> operationProperty = null)
{
if (operationProperty == null) // property of sum/avg (null = count)
operationProperty = x => 1;
if (_operationType.Equals(ReportAggregationOperation.Sum))
{
return query
.GroupBy(g => new
{
Day = TestableDbFunctions.TruncateTime(groupByProperty.Invoke(g))
})
.Select(x => new
{
Key = x.Key.Day.ToString().Substring(0, 10),
Value = (int?) x.Sum(operationProperty.Compile()),
})
.ToDictionary(t => t.Key, t => t.Value);
}
else
{
return query
.GroupBy(g => new
{
Day = DbFunctions.TruncateTime(groupByProperty.Invoke(g))
})
.Select(
x => new
{
Key = x.Key.Day.ToString().Substring(0, 10),
Value = (int?) x.Average(operationProperty.Compile()),
}).ToDictionary(t => t.Key, t => t.Value);
}
}
}
I'm using an IOC container for creating instances like dailyaggreagtor/monthlyaggregator...
But I wasn't able to build this group by expression, or to apply the right design pattern to eliminate the above if statement.
The compile and invoke functions comes from the LinqKit extension.
The classes using this aggregator(s) is querying the DB and collecting data for callcenter reports (ex. TotalCdrRecords report, ReachedCdrRecords report, CdrTalkTime report, CdrCallAfterworkTime etc., a CDR is a call data record, practically holding all information about a call). The type of the query (T) is specified in the report classes, but basically is an IReportableEntity, but of course the operationProperty it can be any property of the DB Entity which we can perfom the count/sum/avg operations, the groupByProperty is always a DateTime column.
It generates the following sql query:
SELECT
1 AS [C1],
SUBSTRING(CASE WHEN ([GroupBy1].[K1] IS NULL) THEN N'' ELSE CAST( [GroupBy1].[K1] AS nvarchar(max)) END, 0 + 1, 10) AS [C2],
CAST( [GroupBy1].[A1] AS int) AS [C3]
FROM ( SELECT
[Filter1].[K1] AS [K1],
SUM([Filter1].[A1]) AS [A1]
FROM ( SELECT
convert (datetime2, convert(varchar(255), [Extent1].[CreatedOn], 102) , 102) AS [K1],
cast(1 as float(53)) AS [A1]
FROM [dbo].[VCCCdr] AS [Extent1]
WHERE ([Extent1].[CreatedOn] >= #p__linq__0) AND ([Extent1].[CreatedOn] <= #p__linq__1) AND ([Extent1].[CompanyId] = #p__linq__2)
) AS [Filter1]
GROUP BY [K1]
) AS [GroupBy1]
Since you are using LINQKit, you can extract an expression for calling Sum / Average part and Invoke it inside the query.
For instance:
var aggregateFunc = _operationType.Equals(ReportAggregationOperation.Sum) ?
Linq.Expr((IEnumerable<double> source) => source.Sum()) :
Linq.Expr((IEnumerable<double> source) => source.Average());
return query
.GroupBy(g => new
{
Day = DbFunctions.TruncateTime(groupByProperty.Invoke(g))
})
.Select(x => new
{
Key = x.Key.Day.ToString().Substring(0, 10),
Value = (int?)aggregateFunc.Invoke(x.Select(operationProperty.Compile())),
})
.ToDictionary(t => t.Key, t => t.Value);

Single database call pulling data from multiple tables in EF Core

The following code currently opens a connection three times to my database, to pull out each object.
Is there a better way to craft the query so the database is only hit once and pulls back all the objects I'm looking for?
var metadataResult = new MetadataViewModel
{
Milestones = goalsContext.Milestones.Select(m => new MilestoneViewModel
{
Id = m.Id,
Name = m.Name,
Year = m.Year,
Date = m.Date
}),
Aggregates = goalsContext.Aggregates.Select(a => new AggregateViewModel
{
Id = a.Id,
Name = a.Name
}),
Metrics = goalsContext.Metrics.Select(m => new MetricViewModel
{
Id = m.Id,
Name = m.Name,
Description = m.Description
})
};
If your view models are a fairly similar shape then you should be able to use Union to get everything in one query and then transform the rows into appropriate ViewModel instances afterwards. Something like the following -
var combinedResults =
context.Products.Select(p => new
{
Type = "Product",
ID = p.ProductID,
Name = p.ProductName,
SupplierName = p.Supplier.CompanyName
})
.Union(
context.Categories.Select(c => new
{
Type = "Category",
ID = c.CategoryID,
Name = c.CategoryName,
SupplierName = (string)null
})
)
.ToList();
var viewModel = new ViewModel
{
Products = combinedResults
.Where(x => x.Type == "Product")
.Select(x => new ProductViewModel
{
ID = x.ID,
Name = x.Name,
SupplierName = x.SupplierName
}),
Categories = combinedResults
.Where(x => x.Type == "Category")
.Select(x => new CategoryViewModel
{
ID = x.ID,
Name = x.Name
})
};

EntityFramework. SelectMany with Anonymous Type and Projection

I have a Banner with multiple Packs. Each pack has multiple files.
I have the following query:
List<BannerModel> models = context.Banners
.Select(x => x.Packs
.SelectMany(p => p.Files, (p, f) => new {
Id = p.Id,
Flag = p.Flag,
File = new { Id = f.Id, Flag = f.Flag, Key = f.Key, Mime = f.Mime }
})
.Where(a => a.File.Flag == "Img_200")
.Select(a => new BannerModel { PackId = a.Id, ImageKey = a.File.Key })
).ToList();
1) I get the error on "ToList()".
Cannot implicitly convert type 'System.Collections.Generic.List>' to 'System.Collections.Generic.List'
2) Then I removed the ToList and added "var models = ..."
I know there are 10 records where 5 of them satisfy the criteria:
.Where(a => a.File.Flag == "Img_200")
What is strange is that I get 10 items, 5 with data and 5 with no data.
Where I should only get a list of 5 items. The one that satisfy the criteria.
Could someone help me solving this problem?
Thank you,
Miguel
Should this be:
List<BannerModel> models = context.Banners
.SelectMany(x => x.Packs
.SelectMany(p => p.Files, (p, f) => new {
Id = p.Id,
Flag = p.Flag,
File = new { Id = f.Id, Flag = f.Flag, Key = f.Key, Mime = f.Mime }
})
.Where(a => a.File.Flag == "Img_200")
.Select(a => new BannerModel { PackId = a.Id, ImageKey = a.File.Key })
).ToList();