Last active
November 29, 2023 21:18
-
-
Save andrewkittredge/ce322944caf5781eb7f4efade5c1266e to your computer and use it in GitHub Desktop.
Join entities with EntityFramework table
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#nullable enable | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using BlazarTech.QueryableValues; | |
using LinqKit; | |
using Microsoft.EntityFrameworkCore; | |
namespace Entities | |
{ | |
public static class JoinWithTableExtensions | |
{ | |
/// <summary> | |
/// Join objects with records from a database table. | |
/// </summary> | |
/// <example> | |
/// <![CDATA[ | |
/// DbContext context = new(); | |
/// List<ExampleEntity> entities = new(); | |
/// var joined = entities.JoinWithTable(db.DBEntities, | |
/// context, | |
/// s => s.entityProperty, | |
/// dbRecord => dbRecord.dbProperty | |
/// ); | |
/// ]]> | |
/// </example> | |
/// <typeparam name="TSource">Type of the objects we are joining with.</typeparam> | |
/// <typeparam name="TTable">Type of the records we are joining with</typeparam> | |
/// <typeparam name="TKey">Type of the key used to join.</typeparam> | |
/// <param name="context">DBContext used to build the QueryableValues</param> | |
/// <param name="table">Objects we want to join with the database records</param> | |
/// <param name="values">Items to join with</param> | |
/// <param name="tableKeySelector">Builds keys from record </param> | |
/// <param name="valueKeySelector">Builds keys from joinable</param> | |
/// <returns>Requested items with the matching/null record from the db.</returns> | |
public static IEnumerable<(TSource source, TTable? dbRecord)> JoinWithTable<TSource, TTable, TKey>( | |
this DbContext context, | |
DbSet<TTable> table, | |
IEnumerable<TSource> values, | |
Expression<Func<TTable, TKey>> tableKeySelector, | |
Func<TSource, TKey> valueKeySelector) | |
where TTable : class | |
{ | |
var queryableValues = context.AsQueryableValues(values.Select(j => valueKeySelector(j))); | |
var joinedWithDB = from dbRecord in table.AsExpandable() | |
join queryableValue in queryableValues | |
on tableKeySelector.Compile()(dbRecord) equals queryableValue | |
into joined | |
from key in joined | |
select new { key, dbRecord }; | |
var joinedWithDBRecords = from j in values | |
join dbRecordAndKey in joinedWithDB | |
on valueKeySelector(j) equals dbRecordAndKey.key | |
into joined | |
from dbRecordAndKey in joined.DefaultIfEmpty() | |
select new ValueTuple<TSource, TTable?>(j, dbRecordAndKey?.dbRecord); | |
Debug.Assert( | |
values.Zip(joinedWithDBRecords, (x, y) => new { x, y.Item1 }) | |
.All(j => j.x.Equals(j.Item1)), | |
"Should be returning all the passed items."); | |
return joinedWithDBRecords; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment