F # — Сделать библиотеку C # более «F #»

#entity-framework #f# #ef-code-first

#entity-framework #f# #ef-code-first

Вопрос:

Каков наилучший способ сделать библиотеку c # более функциональной, если у вас нет доступа к исходному коду? С моей точки зрения, кажется, что у вас есть два варианта: методы расширения или функции, обернутые в модули

… или есть более аккуратный и менее «хакерский» способ выполнить такую задачу?

Примером может быть EF Code First.

Прежде чем я начал создавать функции, упакованные в модули:

 override x.OnModelCreating(modelBuilder:DbModelBuilder) =

    // ----------- FileUpload Configuration ----------- //

    // General
    modelBuilder.Entity<FileUpload>()
        .ToTable("Some")
        |> ignore

    // Key
    modelBuilder.Entity<FileUpload>()
        .HasKey(ToLinq(<@ fun z -> z.ID @>))
        |> ignore

    // Properties
    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Path @>))
        |> ignore

    modelBuilder.Entity<FileUpload>()
        .Property(ToLinq(<@ fun z -> z.Extension @>))
        |> ignore
  

и после:

 override x.OnModelCreating(modelBuilder:DbModelBuilder) =
    let finished = ignore

    // ----------- FileUpload Configuration ----------- //
    let entity = modelBuilder.Entity<FileUpload>()

    // General
    entity
        |> ETC.toTable "Some"

    // Key
    entity 
        |> ETC.hasKey(fun z -> z.ID) 
        |> finished

    // Properties
    entity
        |> ETC.property(fun z -> z.Path)
        |> finished

    entity
        |> ETC.property(fun z -> z.Extension)
        |> finished
  

Модуль, используемый в последнем примере:

 module ETC =
    let property (expr:'a -> string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.Property(ToLinq(<@ expr @>))

    let hasKey (expr:'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasKey(% expr)

    let hasForeignKey (expr: 'a -> 'b) (cfg:DependentNavigationPropertyConfiguration<'a>) = 
        cfg.HasForeignKey(% expr)

    let hasMany (expr: 'a -> ICollection<'b>) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.HasMany(% expr)

    let hasRequired (expr: 'a -> 'b) (cfg:EntityTypeConfiguration<'a>) = cfg.HasRequired(ToLinq(<@ expr @>))

    let withRequired (expr: 'a -> 'a) (cfg:ManyNavigationPropertyConfiguration<'a,'a>) = 
        cfg.WithRequired(% expr)

    let willCascadeOnDelete (cfg:CascadableNavigationPropertyConfiguration) = 
        cfg.WillCascadeOnDelete()

    let isMaxLength(cfg:StringPropertyConfiguration) = 
        cfg.IsMaxLength()

    let toTable (s:string) (cfg:EntityTypeConfiguration<'a>) = 
        cfg.ToTable s
  

Возможно, это не выглядит заметным улучшением, поскольку я не составил полный пример (ленивый я) — Но, как вы видите, последний намного более «функционален» и выглядит более чистым, чем первый.. Но мой вопрос в том, является ли плохой практикой просто переносить методы в функции и переносить эти функции в модули, чтобы обеспечить более функциональный способ использования этих библиотек c #?

Комментарии:

1. Почему бы не написать это на F #, предоставить его программам C # и не беспокоиться об этом.

Ответ №1:

Прежде всего, я не думаю, что ваша оболочка будет работать — в вашей ETC.property вы не можете принять обычную функцию в качестве аргумента, а затем заключить ее в кавычки внутри ETC.property . Вам нужно передать лямбда-функцию как уже заключенную в кавычки (и тип аргумента должен быть Expr<'a -> string> :

 entity |> ETC.property <@ fun z -> z.Path @>
       |> finished
  

К вашему первоначальному вопросу — я думаю, что использование модуля с функциями-оболочками является хорошим вариантом. Вы могли бы немного улучшить синтаксис, передав представление сущности через конвейер, который задает свойства. С соответствующими определениями вы могли бы получить что-то вроде:

 EF.entity<FileUpload> modelBuilder
|> EF.hasKey <@ fun z -> z.ID @>
|> EF.property <@ fun z -> z.Path @>
|> EF.property <@ fun z -> z.Extension @>
|> EF.toTable "Some"
  

Идея в том, что у вас должен быть какой-то тип, EntityInfo<'T> который обертывает то, что вы получаете от вызова modelBuilder.Entity<'T>() . Тогда функции будут иметь такие типы, как:

 EF.hasKey : Expr<'a -> 'b> -> EntityInfo<'T> -> EntityInfo<'T>
EF.toTable : string -> EntityInfo<'T> -> unit
  

Я намеренно использовал unit как результат toTable , потому что это, вероятно, то, что всегда нужно вызывать в любом случае (чтобы мы могли перенести это в конец и избежать явного ignore ). Другие функции просто указывают свойства, а затем возвращают исходный EntityInfo<'T> объект.

Вы могли бы сделать ее еще более навороченной и записать всю спецификацию в виде цитаты. Например:

 modelBuilder |> EF.entity<FileUpload> <@ fun z ->
    EF.hasKey z.ID
    EF.property z.Path
    EF.property z.Extension
    EF.toTable "Some" @>
  

Это не исполняемый код — функции были бы просто фиктивными функциями. Хитрость в том, что вы могли бы обработать цитату и вызвать методы EF на основе этого. Однако это сложнее — вам пришлось бы выполнить довольно много обработки цитат, и это не слишком приятно.