Basic F# / Entity Framework / Generic Functions

时间:2017-08-04 12:52:24

标签: entity-framework generics f# crud

I'm a complete beginner when it comes to development, so apologies in advance for my poor grasp of the terminology here...

I'm trying to create a number of very simple unrelated tables in a SQL database just to get my head round things. I'd like to reference these using Entity Framework, and I'd like to minimise repetition where possible. I'm trying to understand whether there is a more elegant way of doing this so that I don't have to re-write the getters and setters each time:

// First open "System; System.Data; System.Data.Entity; System.ComponentModel.DataAnnotations" 
// Then define "connectionString"
// Then create two simple classes that have no relation to each other, using CLIMutable 
// to help Entity Framework along.

module Model = 
    [<CLIMutable>] type Customer = {[<Key>]Id: int; FirstName: string; LastName: string;}
    [<CLIMutable>] type Item = {[<Key>]Id: int; ItemName: string; ItemDescription: string;}

module Actions =
    open Model
    type Context() =
        inherit DbContext()

        [<DefaultValue>] val mutable customers: DbSet<Customer> 
            member public this.Customers with get() = this.customers
                                           and  set v = this.customers <- v

        [<DefaultValue>] val mutable items: DbSet<Item>
            member public this.Items with get() = this.items
                                           and  set v = this.items <- v

// I would like to be able to add several more very simple classes without having to 
// repetively type out the getters and setters each time. Ideally I'd like to turn the above 
// into a generic function (method? I'm not sure of terminology) that I can call later and 
// pass it my desired parameters in terms of table/object names. However, if I move the code 
// above the Model and replace DbSet<Customer> with DbSet<'T> this won't work since it won't 
// know what type 'T is at this point. I suspect I need a generic function (method?) that 
// I can create a new instance of later and replace the generics with actual values (using 
// reflection?). Is there any way to do this without making Entity Framework get upset?

    let db = new Context()
    db.Database.Connection.ConnectionString <- Settings.connectionString

Similarly I'd like to create a generic function that does the Add() and SaveChanges() steps below, but I'm not sure whether this is possible since I'd need to dynamically replace 'Customers' or 'Items' with a table name that I've passed in - and also the type of 't' could vary depending on what is calling the function and I don't know whether that's allowed.

    let createCustomer id firstName lastName = 
        let t : Customer = {Id = id;FirstName = firstName; LastName = lastName}
        db.Customers.Add(t) |> ignore
        db.SaveChanges() |> ignore

    let createItem id itemName itemDescription = 
        let t : Item = {Id = id; ItemName = itemName; ItemDescription = itemDescription}
        db.Items.Add(t) |> ignore
        db.SaveChanges() |> ignore

open Actions
[<EntryPoint>]
let main argv =     
    createCustomer 1 "Bob" "Smith"
    createItem 1 "First item" "First item test"

    Console.ReadLine() |> ignore
    0

Thanks for any help, understand my questions are probably very basic so if people can't be bothered to re-write code but are able to point me in the right direction of resources for further reading that'd be great!

[edit] Thanks very much to Fyodor for providing the solution. The key was adding the ": Context" type annotation to the "db" element when referring to it generally. Additionally I had made the silly mistake of not opening the "Model" module in my main argument. Updated and working code it as below:

module Model = 
    [<CLIMutable>] type Customer = {[<Key>]Id: int; FirstName: string; LastName: string;}

module Actions =
    open Model
    type Context() =
        inherit DbContext()
        member val Customers: DbSet<Customer> = null with get, set

    let db = new Context()
    db.Database.Connection.ConnectionString <- Settings.connectionString

    let addAndCommit (db : Context) x =
        db.Set().Add x |> ignore
        db.SaveChanges() |> ignore

    let createCustomer id firstName lastName = 
        addAndCommit db {Id = id; FirstName = firstName; LastName = lastName}

open Actions
open Model
[<EntryPoint>]
let main argv =     
    createCustomer 1 "Bob" "Smith"
    Console.ReadLine() |> ignore
    0

1 个答案:

答案 0 :(得分:3)

首先声明带有支持字段的属性,使用member val

type Context() =
    ...
    member val Customers: DbSet<Customer> = null with get, set
    ...

其次,要访问特定类型的DbSet,您不需要属性。您可以通过DbContext.Set<'t>方法获取它:

let addAndCommit (db: Context) (x: 't) =
    db.Set<'t>().Add x |> ignore
    db.SaveChanges() |> ignore

let createCustomer id firstName lastName = 
    addAndCommit db {Id = id; FirstName = firstName; LastName = lastName}

但显式的't注释并不是真正需要的:F#可以为你推断它。

let addAndCommit (db: Context) x =
    db.Set().Add x |> ignore
    db.SaveChanges() |> ignore