由于循环引用而确定如何对 F# 类型进行排序时出现问题
-
27-09-2019 - |
题
我有一些扩展通用类型的类型,这些是我的模型。
然后,我为用于 CRUD 操作的每个模型类型提供 DAO 类型。
我现在需要一个函数,它允许我找到给定任何模型类型的 id,因此我为一些杂项函数创建了一个新类型。
问题是我不知道如何订购这些类型。目前我在 dao 之前有模型,但我需要 DAOMisc
前 CityDAO
和 CityDAO
前 DAOMisc
, ,这是不可能的。
简单的方法是将此函数放入每个 DAO 中,仅引用它之前的类型,因此, State
出现在之前 City
作为 State
与有外键关系 City
, ,所以杂项函数会很短。但是,这让我觉得这是错误的,所以我不确定如何最好地解决这个问题。
这是我的杂项类型,其中 BaseType
是我所有模型的通用类型。
type DAOMisc =
member internal self.FindIdByType item =
match(item:BaseType) with
| :? StateType as i ->
let a = (StateDAO()).Retrieve i
a.Head.Id
| :? CityType as i ->
let a = (CityDAO()).Retrieve i
a.Head.Id
| _ -> -1
这是一种 dao 类型。 CommonDAO
实际上有 CRUD 操作的代码,但这在这里并不重要。
type CityDAO() =
inherit CommonDAO<CityType>("city", ["name"; "state_id"],
(fun(reader) ->
[
while reader.Read() do
let s = new CityType()
s.Id <- reader.GetInt32 0
s.Name <- reader.GetString 1
s.StateName <- reader.GetString 3
]), list.Empty
)
这是我的模型类型:
type CityType() =
inherit BaseType()
let mutable name = ""
let mutable stateName = ""
member this.Name with get() = name and set restnameval=name <- restnameval
member this.StateName with get() = stateName and set stateidval=stateName <- stateidval
override this.ToSqlValuesList = [this.Name;]
override this.ToFKValuesList = [StateType(Name=this.StateName);]
这样做的目的 FindIdByType
功能是我想找到外键关系的 id,因此我可以在模型中设置该值,然后让 CRUD 函数使用所有正确的信息执行操作。所以, City
需要州名称的 id,所以我会获取州名称,将其放入 state
输入,然后调用此函数来获取该州的 id,因此我的城市插入还将包含外键的 id。
这似乎是最好的方法,以一种非常通用的方式来处理插入,这是我当前正在尝试解决的问题。
更新:
我需要研究并看看是否可以在定义所有其他 DAO 后将 FindIdByType 方法注入到 CommonDAO 中,几乎就像它是一个闭包一样。如果这是 Java,我会使用 AOP 来获取我正在寻找的功能,但不确定如何在 F# 中执行此操作。
最终更新:
经过思考我的方法后,我意识到它有致命的缺陷,所以我想出了一种不同的方法。
这就是我将如何进行插入,我决定将这个想法放入每个实体类中,这可能是一个更好的想法。
member self.Insert(user:CityType) =
let fk1 = [(StateDAO().Retrieve ((user.ToFKValuesList.Head :?> StateType), list.Empty)).Head.Id]
self.Insert (user, fk1)
我还没有开始使用 fklist
然而,但是它是 int list
我知道每个列名对应哪个列名,所以我只需要这样做 inner join
例如,对于选择。
这是通用的基本类型刀片:
member self.Insert(user:'a, fklist) =
self.ExecNonQuery (self.BuildUserInsertQuery user)
如果 F# 可以进行协变/逆变,那就太好了,所以我必须解决这个限制。
解决方案
这个例子是什么,我习惯于在函数式编程很远。但对于订购相互递归类型的问题,有一个标准的解决方案:使用类型参数,使两级类型。我给OCaml中,相关语言一个简单的例子。我不知道如何将简单的例子翻译成您所使用的吓人类型的功能。
下面就是不起作用:
type misc = State of string
| City of city
type city = { zipcode : int; location : misc }
下面是你如何与二级类型修复:
type 'm city' = { zipcode : int; location : 'm }
type misc = State of string
| City of misc city'
type city = misc city'
此示例是OCaml的,但也许可以推广到F#。希望这可以帮助。
其他提示
在 F# 中,可以定义 相互递归类型, ,也就是说,你可以将两个需要互相引用的类型定义在一起,他们就会看到对方。编写此内容的语法是:
type CityDAO() =
inherit CommonDAO<CityType>(...)
// we can use DAOMisc here
and DAOMisc =
member internal self.FindIdByType item =
// we can use CityDAO here
此语法的限制是两种类型都需要在单个文件中声明,因此您不能使用典型的 C# 组织每 1 个文件 1 个类型。
正如 Norman 指出的那样,这不是典型的功能设计,因此如果您以更具功能性的方式设计整个数据访问层,则可能可以避免此问题。不过,我想说,在 F# 中结合函数式和面向对象风格并没有什么问题,因此使用相互递归类型可能是唯一的选择。
如果您首先为这两种类型定义接口,您可能可以更好地编写代码 - 这些接口可能需要也可能不需要相互递归(取决于一个类型是否在另一个类型的公共接口中使用):
type ICityDAO =
abstract Foo : // ...
type IDAOMisc =
abstract Foo : // ...
这样做有以下好处:
- 在单个文件中定义所有相互递归接口不会降低代码的可读性
- 您可以稍后引用这些接口,因此其他类型不需要相互递归
- 作为副作用,您将拥有更多可扩展的代码(感谢接口)
F#直接支持相互递归的类型。考虑下面的鸡/蛋类型定义:
type Chicken =
| Eggs of Egg list
and Egg =
| Chickens of Chicken list
的一点是,相互递归类型声明一起使用“和”操作员(而不是两个单独的类型)
如何消除DAOMisc.FindIdByType,并且每个DAO类中具有FindId替换它? FindId只知道如何找到自己的类型。这将消除对基类和动态型测试,并且DAOMisc和所有其他DAO类之间的循环依赖关系的需要。 DAO类型可以依赖于一个另一个,所以CityDAO可以调用StateDAO.FindId。 (DAO类型可取决于彼此相互,如果需要的话)。
这就是你在谈论你的时候说,“简单的方法是把这个功能在每个DAO ...但是,这只是令我错了......”?我不知道,因为你说的那个功能只提及之前而来的类型。那我提出这里的想法是,每个FindId功能只知道自己的类型。