C# 转 F# - EF Code First

8

我有一个一对多的关系,即Dealer可以拥有多个Cars

我尝试将我的EF C#代码转换为F#... 问题只是在我的F#代码中,它可以找到一个经销商,但是它无法获取该经销商... 它只会返回null,但在C#版本中它可以运行?

我的代码:

C#版本

public class Dealer
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Car> Cars { get; set; }

    public Dealer()
    {
        Cars = new List<Car>();
    }
}
public class Car
{
    public int ID { get; set; }
    public string CarName { get; set; }
    public Dealer Dealer { get; set; }
    public int DealerId { get; set; }

    public Car()
    {
        Dealer = new Dealer();
    }
}
public class MyContext : DbContext
{
    public DbSet<Dealer> Dealers { get; set; }
    public DbSet<Car> Cars { get; set; }
    public MyContext()
    {
        Database.Connection.ConnectionString = "server=some;database=dbname;user id=uid;password=pwd";
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Dealer>()
            .HasMany(x => x.Cars)
            .WithRequired(x => x.Dealer)
            .HasForeignKey(x => x.DealerId);

        modelBuilder.Entity<Car>()
            .HasRequired(x => x.Dealer)
            .WithMany()
            .HasForeignKey(x => x.DealerId);

        modelBuilder.Entity<Dealer>().ToTable("Dealers");
        modelBuilder.Entity<Car>().ToTable("Cars");
    }
}

在Program.cs中查询经销商及其汽车:

static void Main(string[] args)
{
    var ctx = new MyContext();
    foreach (var s in ctx.Dealers.FirstOrDefault(x => x.ID == 1).Cars)
    {
        Console.WriteLine(s.CarName);
    }

    Console.Read();
}

F#版本

type Dealer() =
    let mutable id = 0
    let mutable name = ""
    let mutable cars = List<Car>() :> ICollection<Car>
    member x.ID with get() = id and set v = id <- v
    member x.Name with get() = name and set v = name <- v
    abstract member Cars : ICollection<Car> with get, set
    override x.Cars with get() = cars and set v = cars <- v
and Car() =
    let mutable id = 0
    let mutable carName = ""
    let mutable dealer = Dealer()
    let mutable dealerId = 0
    member x.ID with get() = id and set v = id <- v
    member x.CarName with get() = carName and set v = carName <- v
    member x.Dealer with get() = dealer and set v = dealer <- v
    member x.DealerId with get() = dealerId and set v = dealerId <- v


type MyContext() =
    inherit DbContext("server=some;database=dbname;user id=uid;password=pwd")

    [<DefaultValue>] val mutable dealers : DbSet<Dealer>
    member x.Dealers with get() = x.dealers and set v = x.dealers <- v

    [<DefaultValue>] val mutable cars : DbSet<Car>
    member x.Cars with get() = x.cars and set v = x.cars <- v

    override x.OnModelCreating(modelBuilder:DbModelBuilder) =
        modelBuilder.Entity<Dealer>()
            .HasMany(ToLinq(<@ fun ka -> ka.Cars @>))
            .WithRequired(ToLinq(<@ fun sk -> sk.Dealer @>))
            .HasForeignKey(ToLinq(<@ fun fg -> fg.DealerId @>)) |> ignore

        modelBuilder.Entity<Car>()
            .HasRequired(ToLinq(<@ fun ak -> ak.Dealer @>))
            .WithMany()
            .HasForeignKey(ToLinq(<@ fun ka -> ka.DealerId @>)) |> ignore

        modelBuilder.Entity<Dealer>().ToTable("Dealers")
        modelBuilder.Entity<Car>().ToTable("Cars")

...函数ToLinq:

let ToLinq (exp : Expr<'a -> 'b>) = 
    let linq = exp.ToLinqExpression() 
    let call = linq :?> MethodCallExpression
    let lambda = call.Arguments.[0] :?> LambdaExpression 
    Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)

...并且Program.fs将获取经销商及其汽车:

let ctx = new MyContext()
let joe = ctx.Dealers.Find(1)
joe.Cars |> Seq.iter(fun x -> printfn "%s" x.CarName)
printfn "DONE"

非常感谢您的帮助!


我对EF一无所知,但我注意到在F#版本中,Dealer.Cars是一个虚拟属性,而在C#版本中它只是一个普通的属性。如果在F#中更改这个属性会有什么影响吗? - wmeyer
@wmeyer - 是我方面的问题。在 C# 版本中,Dealer.Cars 也应该是虚拟的 - 唯一的变化就是集合现在会惰性加载,这在 c# 中也很好地实现了 (刚刚测试过)。 - ebb
public MyContext() : base("connstring") 这样写可以工作吗? - Robert Jeppesen
@Robert Jeppesen - 说得好.. 真不敢相信你会这么快地忘掉基本的C#知识 ;p - 不过是的,你完全正确。 - ebb
@Ebb - 我知道。我宁愿不用回到C#,但我几乎每天都要用到它。 :) - Robert Jeppesen
显示剩余2条评论
3个回答

5

变更

let mutable cars = List<Car>() :> ICollection<Car>

to

let mutable cars = Unchecked.defaultof<ICollection<Car>>

并且

let mutable dealer = Dealer()   

to

let mutable dealer = Unchecked.defaultof<Dealer>

这实际上是有效的,我认为这很奇怪。C#版本也初始化了这些属性,但那样也有效?有什么见解吗? - Robert Jeppesen
它完美地工作了!你知道为什么我需要执行 Unchecked.defaultof<> 来激活 ICollection 和 Dealer 吗? - ebb
2
F# 是 字段初始化,而 C# 则是 属性初始化,它们是不同的。 - newcommer

1

要检查LINQ表达式是否存在问题,您可以尝试手动创建这样的表达式。 以下F#代码试图模仿在C#代码中使用Reflector或ILSpy可以看到的代码:

open System
open System.Linq.Expressions

// Manually creates a LINQ expression that represents: << x => x.propName >>
// 'T: type of x
// 'C: return type of the property    
let createPropertyGetDelegate<'T, 'C> propName =
   let typ = typeof<'T> // '
   let parameterExpr = Expression.Parameter(typ, "x")
   let getMethod = typ.GetProperty(propName).GetGetMethod()
   let propExpr = Expression.Property(parameterExpr, getMethod)
   Expression.Lambda<Func<'T, 'C>>(propExpr, [|parameterExpr|])


type Example() =
  member x.Test = 42

let del = createPropertyGetDelegate<Example, int> "Test"
printfn "%A" <| del.Compile().Invoke( Example() )

ToLinq 方法运行良好。问题似乎在于 Dealer.Cars 属性未被初始化。@newcommer 似乎已经找到了解决方案 :) - 不过还是谢谢! - ebb

0

我认为问题可能出在你的 F# ToLinq 函数上。看起来你只是将一个 F# 匿名函数转换成一个 MethodCallExpression,并让表达式树像应该调用你的函数一样运行。这与具有 MemberAccessExpression 或从 C# 方面得出的任何其他内容的“真实”表达式树不同。

我认为 EF 希望以更语义化的方式获得表达式树,以便发现实际的属性访问,而不是你在这里进行的简单的“虚假”方法调用。

我的建议是打开 Reflector 并查看两个已编译程序集中表达式树的差异。然后使 F# 代码构建相同类型的表达式树。

免责声明:我对 F# 不是很熟悉,但我想了解 :). 这看起来很酷!


我会尝试并返回结果!:=) - ebb
关于一个完全无关的问题;你是从哪里学到F#语法的?有没有找到什么好的教程网站? - James Dunne
Don Syme的书《Expert F# 2.0》是一本很好的参考书。它有点贵,但是值得购买。Jon Skeet和Thomas Petricek合著了一本不错的书,名为《Real-World Functional Programming With Examples in F# and C#》,非常适合学习如何在实际情况中使用函数式编程,但并不是一个很好的语言参考书。我建议选择前者而不是后者,但两本书都不错。 - Martin Doms
我无法打开Reflector并比较OnModelCreating方法,因为编译器将序列化F# Exprs。关于我从哪里学习了F#语法的问题,主要是通过http://fsharp.net和http://msdn.microsoft.com/en-us/library/dd233154.aspx这两个网站指引我的方向。 - ebb

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