Azure Blob- and Table Storage
Let's use the WorkerRole from previous tutorials and also Windows Azure Storage -library. (Earlier version of this tutorial used Fog-library but it didn't work well with the latest Azure SDKs.)
First task is to add connection strings to Azure storage. It is done like this:
Open the deployment-project file ServiceDefinition.csdef and add there settings: TableStorageConnectionString and BlobStorageConnectionString. In the Azure emulator -environment these can be empty.
File content attribute name="FSharpAzure"
refers to the name of the solution and schemaVersion="2013-10.2.2"
refers to the used AzureSDK, here 2.2, but 2.3 would be schemaVersion="2014-01.2.3"
and 2.4 would be schemaVersion="2014-06.2.4"
.
<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="FSharpAzure"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
schemaVersion="2013-10.2.2">
<WorkerRole name="WorkerRole1" vmsize="ExtraSmall">
<Endpoints>
<InputEndpoint name="Endpoint1" protocol="http" port="80" localPort="80" />
</Endpoints>
<Imports>
<Import moduleName="Diagnostics" />
</Imports>
<ConfigurationSettings>
<Setting name="TableStorageConnectionString" />
<Setting name="BlobStorageConnectionString" />
</ConfigurationSettings>
</WorkerRole>
</ServiceDefinition>
Modify also the files ServiceConfiguration.Cloud.cscfg and ServiceConfiguration.Local.cscfg to contain the corresponding settings:
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="FSharpAzure"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
<Role name="WorkerRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
value="UseDevelopmentStorage=true" />
<Setting name="TableStorageConnectionString" value="UseDevelopmentStorage=true" />
<Setting name="BlobStorageConnectionString" value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration>
For emulator, it is OK to use value="UseDevelopmentStorage=true", but in the production this connection-string should be something else. For this, there is tutorial in the net.
Using F-Sharp script-files
Using these codes in interactive-environment works, but execution of the actual Azure-calls won't. So you may add to beginning of the file:
1: 2: 3: 4: 5: |
#if INTERACTIVE #r "../packages/Fog.0.1.4.1/Lib/Net40/Fog.dll" #r "System.Data.Services.Client.dll" #r "Microsoft.WindowsAzure.StorageClient.dll" #endif |
Note that NuGet may have fetched you different version of the component than in this example. So if Visual Studio underlines the line with red, and says the file doesn't exist, this may be the result of you having newer version of the component, in another path. Another commonly used practice in F# is to add one file of type Script File (*.fsx) to the project. This runs only in interactive-style scripting but will be excluded from the actual compilation. These #r:s works also there and project's .fs-files could be loaded like this:
1: 2: |
#load "MyLogics.fs" open MyLogics |
Caching and settings
This code works with Blob and Table -storages. Open the logics file and add on this code to it:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
module MyLogics open System open Microsoft.WindowsAzure.ServiceRuntime open Microsoft.WindowsAzure.Storage open System.Collections.Concurrent let dict = ConcurrentDictionary() //for caching let fetchSetting = RoleEnvironment.GetConfigurationSettingValue >> CloudStorageAccount.Parse |
We use memoize for caching. Memoize/memoization caches the whole function call and not only input parameters like normal caching.
Azure Blob Storage
Blob-storage is a simple data storage e.g. for files. Add this code under the previous:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: |
let blobConnection (container:string) = let account = fetchSetting "BlobStorageConnectionString" let client = account.CreateCloudBlobClient() client.GetContainerReference(container.ToLower()) let uploadBlobToContainer containerName blobName (item:string) = let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force() let container = memoize(fun cont -> blobConnection cont) containerName async { let! ok = container.CreateIfNotExistsAsync() |> Async.AwaitTask let blob = container.GetBlockBlobReference(blobName) let enc = System.Text.Encoding.ASCII.GetBytes(item) use ms = new System.IO.MemoryStream(enc, 0, enc.Length) do! blob.UploadFromStreamAsync ms |> Async.AwaitIAsyncResult |> Async.Ignore } let addToBlob = uploadBlobToContainer "testContainer" "testBlob" open System open FSharp.Data let demoData = let ``scientist of the day `` = FreebaseData.GetDataContext().Commons.Computers.``Computer Scientists`` |> Seq.skip DateTime.Now.DayOfYear // |> Seq.head "Scientist of the day: " + ``scientist of the day ``.Name |
Now you may call this from the WorkerRole.fs file's method wr.OnStart():
1:
|
MyLogics.demoData |> MyLogics.addToBlob |
When you start the program (F5), you may find from Server Explorer (not Solution Explorer) that (after refresh) there has been appeared a blob which you can double-click to open, and there is the blob-list that you can again double-click to open the data itself:
Azure Table Storage
Azure Table Storage is NoSQL-minded data storage.
Here is a code sample to use it:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: |
let ``Azure dvd table`` = "Dvd" [<Measure>] type stars open Microsoft.WindowsAzure.Storage.Table type MyDvdEntity(partitionKey, rowKey, name, rating) = inherit TableEntity(partitionKey, rowKey) new(name, rating) = MyDvdEntity("defaultPartition", System.Guid.NewGuid().ToString(), name, rating) new() = MyDvdEntity("", 0<stars>) member val Name = name with get, set member val Rating = rating with get, set let tableConnection tableName = let account = fetchSetting "TableStorageConnectionString" let client = account.CreateCloudTableClient() client.GetTableReference(tableName) let doAction tableName operation = let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force() let table = memoize(fun tb -> tableConnection tb) tableName async { let! created = table.CreateIfNotExistsAsync() |> Async.AwaitTask return! table.ExecuteAsync(operation) |> Async.AwaitTask } let addDvd name rating = let dvd = MyDvdEntity( PartitionKey = "myPartition", RowKey = System.Guid.NewGuid().ToString(), Name = name, Rating = rating ) dvd, dvd |> TableOperation.Insert //Insert, Delete, Replace, etc. |> doAction ``Azure dvd table`` |> Async.RunSynchronously let updateDvd dvd = dvd |> TableOperation.Replace |> doAction ``Azure dvd table`` |> Async.RunSynchronously let deleteDvd dvd = dvd |> TableOperation.Delete |> doAction ``Azure dvd table`` |> Async.RunSynchronously |
...and corresponding calls to WorkerRole.fs to OnStart-method before base.OnStart, e.g.:
1: 2: 3: |
let dvd, result = MyLogics.addDvd "Godfather" 4<stars> dvd.Rating <- 5<MyLogics.stars> let result2 = MyLogics.updateDvd dvd |
The actual Table Storage query language is very limited and the new Table Service Layer doesn't support LINQ so the queries has to be done with TableOperation.Retrieve-method, or constructing separate TableQuery and execute it with ExecuteQuery-method.
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
member BodyName : string
member Clone : unit -> obj
member CodePage : int
member DecoderFallback : DecoderFallback with get, set
member EncoderFallback : EncoderFallback with get, set
member EncodingName : string
member Equals : value:obj -> bool
member GetByteCount : chars:char[] -> int + 3 overloads
member GetBytes : chars:char[] -> byte[] + 5 overloads
member GetCharCount : bytes:byte[] -> int + 2 overloads
...
Full name: System.Text.Encoding
System.Text.Encoding.GetBytes(chars: char []) : byte []
System.Text.Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
System.Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
System.Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
System.Text.Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
type MemoryStream =
inherit Stream
new : unit -> MemoryStream + 6 overloads
member CanRead : bool
member CanSeek : bool
member CanWrite : bool
member Capacity : int with get, set
member Flush : unit -> unit
member GetBuffer : unit -> byte[]
member Length : int64
member Position : int64 with get, set
member Read : buffer:byte[] * offset:int * count:int -> int
...
Full name: System.IO.MemoryStream
--------------------
System.IO.MemoryStream() : unit
System.IO.MemoryStream(capacity: int) : unit
System.IO.MemoryStream(buffer: byte []) : unit
System.IO.MemoryStream(buffer: byte [], writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : unit
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.skip
Full name: Microsoft.FSharp.Collections.Seq.head
type MeasureAttribute =
inherit Attribute
new : unit -> MeasureAttribute
Full name: Microsoft.FSharp.Core.MeasureAttribute
--------------------
new : unit -> MeasureAttribute
type Guid =
struct
new : b:byte[] -> Guid + 4 overloads
member CompareTo : value:obj -> int + 1 overload
member Equals : o:obj -> bool + 1 overload
member GetHashCode : unit -> int
member ToByteArray : unit -> byte[]
member ToString : unit -> string + 2 overloads
static val Empty : Guid
static member NewGuid : unit -> Guid
static member Parse : input:string -> Guid
static member ParseExact : input:string * format:string -> Guid
...
end
Full name: System.Guid
--------------------
System.Guid()
System.Guid(b: byte []) : unit
System.Guid(g: string) : unit
System.Guid(a: int, b: int16, c: int16, d: byte []) : unit
System.Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set