knocte
knocte

Reputation: 17939

Join with Entity Framework and F#

What seems to be an easy task in C# doesn't seem so easy in F#...

Given the following types in C# I want to make an inner join with F#:

public partial class Foo
{
    public long FooID { get; set; }
    public long BarID { get; set; }
    public bool SomeColumn1 { get; set; }
}

public partial class Bar
{
    public long ID { get; set; }
    public string SomeColumn1 { get; set; }
    public bool SomeColumn2 { get; set; }
}

So my try at doing this is:

let dbContext = DatabaseManager.Instance.ProduceContext()

let barIdForeignKey(f: Foo) =
    f.BarID

let barIdPrimaryKey(b: Bar) =
    b.ID

let joinResult(f: Foo, b: Bar) =
    (f, b)

let joinedElements =
    dbContext.foos.Join(dbContext.bars, barIdForeignKey, barIdPrimaryKey, joinResult)

But the compiler complains with something like:

Possible overload: (extension)

System.Collections.Generic.IEnumerable.Join<'TOuter, 'TInner, 'TKey, 'TResult>(
    inner:            System.Collections.Generic.IEnumerable<'TInner>,
    outerKeySelector: System.Func<'TOuter, 'TKey>,
    innerKeySelector: System.Func<'TInner, 'TKey>,
    resultSelector:   System.Func<'TOuter, 'TInner, 'TResult>)
  : System.Collections.Generic.IEnumerable<'TResult>

Type constraint mismatch. The type 'd * 'e -> foo * bar is not compatible with type System.Func<'a, 'b, 'c>

The type 'd * 'e -> foo * bar is not compatible with the type System.Func<'a, 'b, 'c>

Not sure how to read this. Maybe it's that I cannot return a tuple at the end? In C# I would need an anonymous type, like new { Foo = foo, Bar = bar }, not sure how to do that in F#.

Upvotes: 3

Views: 780

Answers (1)

knocte
knocte

Reputation: 17939

Solution (thanks @ildjarn) using currified arguments for the last func:

let dbContext = DatabaseManager.Instance.ProduceContext()

let barIdForeignKey(f: Foo) =
    f.BarID

let barIdPrimaryKey(b: Bar) =
    b.ID

let joinResult(f: Foo) (b: Bar) =
    (f, b)

let joinedElements =
    dbContext.foos.Join(dbContext.bars, barIdForeignKey, barIdPrimaryKey, joinResult)

Which in the end can be simplified to:

let joinedElements =
    dbContext.foos.Join (dbContext.bars,
                         (fun f -> f.barID),
                         (fun b -> b.ID),
                         (fun f b -> (f,b))
                        )

@PanagiotisKanavos also gave me a hint about joins using an ORM being a code-smell, and that let me discover that there was actually a Bar property in the Foo class (so that I don't need to fiddle with BarID column, as this Bar property is populated under the hood by EntityFramework this way anyway). This, combined with @ildjarn's original suggestion for using query expressions, led me to the best answer:

let dbContext = DatabaseManager.Instance.ProduceContext()

let joinedElements =
    query {
        for foo in dbContext.foos do
            select (foo.Bar, foo.SomeColumn1)
    }

Upvotes: 3

Related Questions