fsprojects / FSharp.Azure.Storage

F# API for using Microsoft Azure Table Storage service
MIT License
75 stars 16 forks source link

Support merging for partial record properties #16

Open eiriktsarpalis opened 9 years ago

eiriktsarpalis commented 9 years ago

I have identified a slight shortcoming in the way that the merge operation is exposed in the library. The problem is that you are always forced to update each and every property of the record representation, which can be problematic in certain applications. For example, consider the record:

type Record = { [<PartitionKey>] : string; [<RowKey>] : string ; Value1 : int; Value2 : int }

Supposing I only wanted to merge with respect to the second value, I would be have to be forced to fetch the original record, update the value in question, and then perform a full merge on the record properties.

As it is, the only way to overcome the problem is to create a second RecordPartial type that only contains the updated value in question, but I find this approach to be a bit problematic.

I've come up with the following idea. We could append the operation:

type Operation<'T> =
   ...
   | MergePartial of Expr<'T -> 'T> * etag:string
   ...

which could be used as follows:

    MergePartial <@ fun record -> { record with Value2 = 42 } @>

This would allow the library to build partial entities for merging in the expected way. Obviously it requires a bit of quotation manipulation to achieve this but in my experiments I've become convinced it is perfectly doable.

daniel-chambers commented 9 years ago

This is a cool idea!

Under the covers, the simplest way to implement this is probably to read that quotation's structure and populate a DynamicTableEntity and push that through a regular Merge.

We'd need to be careful to ensure that the caller always specifies the Partition Key and Row Key properties on that record in the quotation, otherwise we've no way of actually addressing the row they want to update. This is a harder than it looks at first pass, though.

There's an annoying case with record types that are using EntityIdentifierReader.GetIdentifier; we won't be able to detect which properties are required as PK and RK. Maybe we can create an instance of the record with defaults for all other fields and run the function over it to get the PK/RK and check if they come back null or ""?

The same scenario exists for types that implement IEntityIdentifiable (in fact, all PK/RK reading is done as a default EntityIdentiferReader.GetIdentifier function).