Skip to content

Instantly share code, notes, and snippets.

@Thorium
Last active July 1, 2024 09:40
Show Gist options
  • Save Thorium/b52ba55218d2b3f23220ef7f7a4aa6be to your computer and use it in GitHub Desktop.
Save Thorium/b52ba55218d2b3f23220ef7f7a4aa6be to your computer and use it in GitHub Desktop.
DBContext independend LINQ, the functional way
/// Old post recovered from Feb 2013:
/// https://web.archive.org/web/20130510043355/social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/bc0bd8f3-0e49-440c-b0bb-8d9adb593934
///
/// This shows how you can:
///
/// - Code from top-down, give high level picture first and then go to details
/// - Define LINQ outside EF-context and still convert it to SQL: Don't hammer the database!
/// - Have small independend classes (/parts)
/// - Have state-less code with no global/class-variables outside entities
/// - Easy to test: a) Integration test with EF-DbContext.
/// b) LINQ logics unit tests without DbContext (with unit tests InternalsVisibleToAttribute).
/// - Code declarative and functional C#
///
/// (Applying some F#-language concepts to C#. Like function composition and partial application)
///
/// <summary> Some simple EF domain item for this example</summary>
public class MyItem
{
public string Name { get; set; }
public int Age { get; set; }
public string AccountNumber { get; set; }
}
/// <summary> Some simple EF DBContext item for this example </summary>
public class MyDbContext : DbContext
{
public MyDbContext() : base("myConnectionString") { }
public DbSet<MyItem> MyItems;
}
/// <summary>
/// The first class that user should open when opening solution...
/// This gives you the high level business-oriented picture (with minimal .NET-implementation details)
/// </summary>
public static class Program
{
/// <summary> Top-down coding: High leven picture </summary>
public static void HighLevelBusinessPicture()
{
var res = MyBusinessOperation
.DefineMyBusinessAsLinq()
.DefineSomeMoreBusinessAsLinq()
.DoSomethingAndExecuteAll();
}
}
internal static class MyBusinessOperation
{
/// <summary> This is pure EF wihout context. </summary>
/// <returns> Returns a function which input is Context.DbSet items, output is result as Queryable</returns>
internal static Func<IQueryable<MyItem>, IQueryable<string>> DefineMyBusinessAsLinq()
{
return items =>
{
var namesAndAccounts =
from i in items
where i.Age > 10
select new { i.Name, i.AccountNumber }; // (note: EF-limitation: no complex objects here...)
var externals =
from pair in namesAndAccounts
where !string.IsNullOrEmpty(pair.AccountNumber)
select pair.AccountNumber;
return externals;
};
}
}
/// <summary> Some other independend operation. </summary>
internal static class MyBusinessOperation2
{
/// <summary> Again, the same pattern here! </summary>
internal static Func<IQueryable<MyItem>, IQueryable<string>> DefineSomeMoreBusinessAsLinq(this Func<IQueryable<MyItem>, IQueryable<string>> accountNumbers)
{
return items =>
{
var res = from acc in accountNumbers(items)
let externalAccount = "External " + acc
select externalAccount;
return res;
};
}
}
/// <summary> Then, after everything is ok, we will </summary>
internal static class MyBusinessOperation3
{
internal static IEnumerable<string> DoSomethingAndExecuteAll(this Func<IQueryable<MyItem>, IQueryable<string>> inputs)
{
using (var context = new MyDbContext())
{
var execute = inputs(context.MyItems.AsQueryable());
return execute.ToList();
}
}
// /// <summary> Or EntityFramework 6 Asyc version, await when needed: </summary>
//public async static Task<IEnumerable<string>> DoSomethingAndExecuteAll(this Func<IQueryable<MyItem>, IQueryable<string>> inputs)
//{
// using (var context = new MyDbContext())
// {
// var execute = inputs(context.MyItems.AsQueryable());
// return await execute.ToListAsync().ContinueWith(r => r.Result.AsEnumerable());
// }
//}
}
///// <summary>
///// Example of unit test
///// </summary>
//[TestClass]
//public class MyBusinessOperation2Fixture
//{
// [TestMethod]
// public void DefineSomeMoreBusinessAsLinqTest()
// {
// Func<IQueryable<MyItem>, IQueryable<string>> inputAccounts = q => Enumerable.Repeat("hello", 1).AsQueryable();
// var business = inputAccounts.DefineSomeMoreBusinessAsLinq();
// var result = business(null).ToList().First();
// Assert.AreEqual("External hello", result);
// }
//}
/// -----------------
/// This idea is used in real production of an enterprise system.
/// The extension methods are in separate small classes in separate folders.
/// Our developers develop (in parallel) different methods with integration/unit testing (coverage over 85%).
/// This will suite best with scenarios where you separate your queries from your updates (CQS or CQRS).
/// Usually the methods are ok with only one input and one output. And there is no common floating state between methods
/// (which could be avoided with state-monad, if there would). To simplify C#-type-syntax, in some cases there are also separate
/// (immutable) classes as parameter-classes (=the classes that these high level methods extend). Note that my business methods
/// are not independent: the order matters. If there would be some kind of independent methods, then I would run them as parallel,
/// maybe return Task and use it as some kind of explicit synchronization context.
///
/// Basic ideological concept: https://github.com/Basware/LINQ-Tutorial
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment