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
First, to declare a property with a backing field, use member val:
type Context() =
...
member val Customers: DbSet<Customer> = null with get, set
...
Second, to access a DbSet of a specific type, you don't need a property. You can get it via DbContext.Set<'t> method:
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}
But the explicit 't annotation is not really required: F# can infer it for you.
let addAndCommit (db: Context) x =
db.Set().Add x |> ignore
db.SaveChanges() |> ignore
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With