Skip to content

Instantly share code, notes, and snippets.

@andrewkittredge
Last active November 29, 2023 21:18
Show Gist options
  • Save andrewkittredge/ce322944caf5781eb7f4efade5c1266e to your computer and use it in GitHub Desktop.
Save andrewkittredge/ce322944caf5781eb7f4efade5c1266e to your computer and use it in GitHub Desktop.
Join entities with EntityFramework table
#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