Dapper icon indicating copy to clipboard operation
Dapper copied to clipboard

support F# anonymous types

Open enricosada opened this issue 5 years ago • 27 comments

Support F# anonymous types in query like

let res = db.QuerySingle<{| Tid: int; Name: string; X:string; Y: int |}>("select Tid=20, Name='fdsf', X='hfds', Y=15")  
  • The search for constructor fallback to search by names sorted, if not found by position.
  • The instantiation of the constructor must respect order or constructor argument

To initialize the constructor argument correctly, i first tried to reoder the il fields doing the read from datareader to match constructor arguments position, not datareader name position, but i got:

System.InvalidOperationException : Invalid attempt to read from column ordinal '0'.  With CommandBehavior.SequentialAccess, you may only read from column ordinal '2' or greater.

so cannot be done like this, because read need to be sequential by column index.

To maintain the read order, before initializing the constructor, if needed, it will:

  • pop from the stack who contains the constructor args to new local variables (the order is known, because is always by column names index)
  • push back in the stack the values from the local variables, in the correct order based on constructor arguments position

fix https://github.com/StackExchange/Dapper/issues/1226

enricosada avatar Apr 07 '19 10:04 enricosada

done, @mgravell @NickCraver ready to review

enricosada avatar Apr 08 '19 08:04 enricosada

@mgravell anything i can help with for this PR?

enricosada avatar May 23 '19 12:05 enricosada

@NickCraver @mgravell any specific issue with this PR i need to address? happy to help to have it merged

sorry but anonymous types in f# are really handy with dapper, but are really bugged without this PR

enricosada avatar Sep 25 '19 16:09 enricosada

I totally understand. It isn't personal - I simply haven't had chance to look yet - I'm juggling time between multiple projects. The existence of tests in the PR is great. I'll try and look for the next drop. Sorry for delays.

mgravell avatar Sep 25 '19 22:09 mgravell

Np at all @mgravell , i know it may happen, it was just to check if there was something todo to help

meanwhile i'll rebase it

thanks a lot!

enricosada avatar Sep 26 '19 12:09 enricosada

@enricosada did you see Marc's comment above? We'd love to get this in, but it could be a fair bit more efficient if intent matches.

NickCraver avatar Oct 21 '19 12:10 NickCraver

@NickCraver no, i missed that sry, let me check. thx for ping

enricosada avatar Oct 21 '19 12:10 enricosada

@mgravell i added some comments, because i not understood your feedback @NickCraver happy to fix, but dunno how to improve this atm, it fix the bug atm and i didnt see a big perf hit for F#

enricosada avatar Nov 21 '19 20:11 enricosada

Hi @enricosada - I got nudged by a mutual friend to remind me to look at this again; sorry for delay. Can I check - the confusion we're talking about - is this the zip vs dual sort vs arrays thing?

My understanding - and please do correct me if I'm wrong here - is that we have two arrays; one of the names, one of the types, matching by index; so if we have 3 columns, we might have

names: "def", "ghi", "abc" types: Int32, String, Single

as I read the code, the intent here is to sort by the names, preserving the respective positions in both arrays, so we end up with:

names: "abc", "def", "ghi" types: Single, Int32, String

is that correct? If so, that's exactly what the dual array version of Array.Sort does:

        string[] names = { "def", "ghi", "abc" };
        Type[] types = { typeof(int), typeof(string), typeof(float) };

        Array.Sort(names, types);

        Console.WriteLine($"names: {string.Join(", ", names)}");
        Console.WriteLine($"types: {string.Join(", ", types.Select(x => x.Name))}");

        /* output:
            names: abc, def, ghi
            types: Single, Int32, String
         */

Have I misunderstood?

mgravell avatar Jan 08 '20 13:01 mgravell

Have I misunderstood?

hi @mgravell

Maybe i didnt understood what the dual array version of Array.Sort does. I tried locally when you mentioned it but didnt understood.

I think that's exactly what i want, sort two arrays based on values of first array and maintaining the sync.

Let me rebase and use Array.Sort

enricosada avatar Jan 08 '20 13:01 enricosada

sort two arrays based on values of first array and maintaining the sync

to confirm, yes: that is an accurate description of what Array.Sort does with the overload that takes two arrays

mgravell avatar Jan 08 '20 13:01 mgravell

@mgravell rebased and applied your feedback, it's now using Array.Sort instead of zip+sort I tested locally with other cases and seems fine too. So if appveyor is green, should be ok (the PR contains a test anyway)

Thanks a lot btw @mgravell , TIL about Array.Sort overload :D

enricosada avatar Jan 08 '20 15:01 enricosada

Can I ask about the status of this? I would like to use this feature in my project and can clone this branch for now, but only if it is expected to make it into master.

TravisLeithVeloqx avatar Feb 09 '20 04:02 TravisLeithVeloqx

I just got hit by this as well. Would be awesome if it made it in a new release!

atlemann avatar Mar 26 '20 20:03 atlemann

@mgravell i think this is ready for review and merge.

happy to do any change required

enricosada avatar Mar 30 '20 15:03 enricosada

I keep coming up against this issue. Is there a release schedule in mind?

TravisLeithVeloqx avatar Apr 15 '20 09:04 TravisLeithVeloqx

will re-review; sorry for dropping between cracks

mgravell avatar Apr 15 '20 13:04 mgravell

Code seems good; checking the tests locally for regressions - seems fine; you're happy that it is working from F#?

mgravell avatar Apr 15 '20 13:04 mgravell

@mgravell @enricosada Until now I have not actually attempted to use this. Today I pulled this branch and built it. I added the Dapper.dll to my the project I am currently working on and attempted to use an anonymous record. I get the same parameterless default constructor error.

The offending code is

let parameters = Map ["ids", box ids; "dates", box dates]
use conn = new MySqlConnection(connectionString)

conn
|> dapperMapParametrizedQuery<{|id:int; date:DateTime; variance:float|}> sql parameters
|> Seq.groupBy (fun x -> x.date)
|> Seq.map (fun (dt, sq) -> dt, sq |> Seq.map (fun x -> {id = x.id; variance = x.variance})) |> Ok

So either I have built it wrong, or am using it wrong.

TravisLeithVeloqx avatar Apr 16 '20 06:04 TravisLeithVeloqx

Let me check your repro

From mobile

-- Enrico Sada @enricosada


Da: TravisLeithVeloqx [email protected] Inviato: Thursday, April 16, 2020 8:59:59 AM A: StackExchange/Dapper [email protected] Cc: Enrico Sada [email protected]; Mention [email protected] Oggetto: Re: [StackExchange/Dapper] support F# anonymous types (#1233)

@mgravellhttps://github.com/mgravell @enricosadahttps://github.com/enricosada Until now I have not actually attempted to use this. Today I pulled this branch and built it. I added the Dapper.dll to my the project I am currently working on and attempted to use an anonymous record. I get the same parameterless default constructor error.

The offending code is

let parameters = Map ["ids", box ids; "dates", box dates] use conn = new MySqlConnection(connectionString)

conn |> dapperMapParametrizedQuery<{|id:int; date:DateTime; variance:float|}> sql parameters |> Seq.groupBy (fun x -> x.date) |> Seq.map (fun (dt, sq) -> dt, sq |> Seq.map (fun x -> {id = x.id; variance = x.variance})) |> Ok

So either I have built it wrong, or am using it wrong.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/StackExchange/Dapper/pull/1233#issuecomment-614453188, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AABD6K5GVU7DEJWOPXLI77DRM2UG7ANCNFSM4HECYHLQ.

enricosada avatar Apr 16 '20 14:04 enricosada

@enricosada is this working?

travis-leith avatar Jun 23 '20 08:06 travis-leith

I updated this branch to latest main - still prepared to merge but we don't use F# and would really appreciate some community feedback on whether this works as intended.

NickCraver avatar May 09 '21 13:05 NickCraver

@NickCraver thanks a lot.

I’ll check tomorrow morning

enricosada avatar May 09 '21 13:05 enricosada

@enricosada How can I test it? Should it work with unordered ctor parameters?

brase avatar Jul 21 '21 07:07 brase

pew, has been a while. Maybe I should take a look into that.

realvictorprm avatar Oct 27 '23 15:10 realvictorprm

@enricosada How can I test it? Should it work with unordered ctor parameters?

So general suggestion from my side, I would have gone down the route and added another test folder with an F# project that depends on Dapper and uses it with anon records to make sure that the feature is functional. Anything against going that far? Alternatively an F# library project could be added, that contains an anon record with a type alias, such that it can be consumed by C# and used for directly piping into the C# functions to test.

realvictorprm avatar Oct 30 '23 09:10 realvictorprm

@NickCraver are you still open for having this change? If yes, could we reiterate on what you want to see from your side to be changed to get it merged, considering how long it has been since work has been done on this PR & topic?

realvictorprm avatar Nov 01 '23 10:11 realvictorprm