Last active
July 1, 2024 09:40
-
-
Save Thorium/b52ba55218d2b3f23220ef7f7a4aa6be to your computer and use it in GitHub Desktop.
DBContext independend LINQ, the functional way
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
/// 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