使用Entity Framework和F#进行连接

3

在C#中看似容易的任务,在F#中却不那么容易...

给定以下C#类型,我想使用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; }
}

所以我尝试做的是:
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)

但是编译器会报错,类似于:

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>

不确定如何阅读这个。也许是因为我不能在结尾处返回元组?在C#中,我需要一个匿名类型,比如new { Foo = foo, Bar = bar },但我不确定如何在F#中实现。


4
为什么不使用查询表达式?无论如何,尝试将let joinResult(f: Foo, b: Bar) =更改为let joinResult (f: Foo) (b: Bar) =。 (意思是建议在代码中使用查询表达式,并将一个带有两个参数的函数更改为两个带有单个参数的函数) - ildjarn
2
当使用ORM时,连接是一种非常强烈的气味。这意味着映射有误并且缺少一些重要的关系。如果Foo有一个Bar属性,您就不需要连接。EF会生成适当的SQL来加载相关实体。您可以控制相关实体是急切地加载还是懒惰地加载。 - Panagiotis Kanavos
2
F# 通常会在方法调用时自动将 F# 函数转换为 .NET 委托类型(例如 Func<_,_,_>),但您需要使用柯里化而不是元组输入(例如 let joinResult (f:Foo) (b:Bar) = ...)。 - kvb
1
@knocte: 你的问题与EF无关,它与编译器将F#函数转换为.NET委托的方式有关。 - ildjarn
1
当处理EntityFramework的C#生成实体时,许多人可能会遇到这个问题,因此我认为提及/标记它是很好的。 - knocte
显示剩余6条评论
1个回答

3

感谢 @ildjarn 提供的解决方案,使用柯里化参数来改进最后一个函数:

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)

最终可以简化为:
let joinedElements =
    dbContext.foos.Join (dbContext.bars,
                         (fun f -> f.barID),
                         (fun b -> b.ID),
                         (fun f b -> (f,b))
                        )

@PanagiotisKanavos也给了我一个提示,说使用ORM进行连接是一种代码异味,这让我发现Foo类中实际上有一个Bar属性(因此我无需操作BarID列,因为这个Bar属性在EntityFramework下面被自动填充)。结合@ildjarn的原始建议使用查询表达式,让我找到了最好的答案:

let dbContext = DatabaseManager.Instance.ProduceContext()

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接