Snippets

Tuomas Hietanen DBContext independend LINQ, the functional way

Created by Tuomas Hietanen
/// 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

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.