我们有很多代码通过“IDS“数据行;这些主要是整体或指导。通过创建一个,我可以使这个代码更安全 不同的结构 为每个数据库表的id。 然后类型检查器将帮助查找传递错误ID的情况。
例如,Person表有一个列调用PersonId,我们的代码如下:
DeletePerson(int personId)
DeleteCar(int carId)
拥有以下内容会更好吗:
struct PersonId
{
private int id;
// GetHashCode etc....
}
DeletePerson(PersionId persionId)
DeleteCar(CarId carId)
有没有人有真实的生活经历
这个?
是否值得开销?
或者更痛苦那么值得吗?
(它还可以更容易地更改主键数据库中的数据类型,这是我首先想到的这种理想方式)
请不要说使用ORM对系统设计进行一些其他重大改动,因为我知道ORM会是一个更好的选择,但目前我的力量并不是我的权力。但是我可以对上面正在进行的模块进行如上所述的微小更改。
更新:
请注意,这不是一个Web应用程序,并且Ids保存在内存中并通过WCF传递,因此没有转换到/来自边缘的字符串。没有理由WCF接口不能使用PersonId类型等.PersonsId类型等甚至可以在WPF / Winforms UI代码中使用。
唯一固有的 “类型化” 系统的一部分是数据库。
这似乎取决于花时间编写代码可以更好地检查代码或花费时间编写更多单元测试的成本/收益。我更倾向于花时间进行测试,因为我希望在代码库中至少看到一些单元测试。
我不会为此特别提出异议。这主要是一个测试问题。您可以测试代码并确保它完成预期的操作。
您可以通过传入要操作的整个对象,在系统中创建一种标准的处理方式,而不是帮助将来的维护(类似于您提到的)。当然,如果你命名你的参数(int personID)并且有文档,那么任何非恶意程序员应该能够在调用该方法时有效地使用代码。传递整个对象将执行您正在寻找的类型匹配,这应该足够标准化的方式。
我只是看到有一个特殊的结构来防止这种情况,因为增加了更多的工作而没什么好处。即使你这样做了,也有人可以找到一种方便的方法来制定一个“帮助”方法并绕过你所设置的任何结构,所以它实际上不是一个保证。
很难看出它是如何值得的:我建议只作为最后的手段,并且只有当人们在开发过程中实际混合标识符或报告难以保持标准时才这样做。
特别是在Web应用程序中,它甚至不会提供您希望的安全性:通常您无论如何都会将字符串转换为整数。有太多的情况你会发现自己编写如下的愚蠢代码:
int personId;
if (Int32.TryParse(Request["personId"], out personId)) {
this.person = this.PersonRepository.Get(new PersonId(personId));
}
处理内存中的复杂状态肯定会改善强类型ID的情况,但我认为 亚瑟的想法 甚至更好:为了避免混淆,需要实体实例而不是标识符。在某些情况下,性能和内存考虑可能会使这种做法变得不切实际,但即使是那些也应该是非常罕见的,以至于代码审查在没有负面副作用的情况下同样有效(完全相反!)。
我曾经在一个这样做的系统上工作,并没有真正提供任何价值。我们没有像您所描述的那样含糊不清,并且在未来验证方面,它使得在没有任何回报的情况下实现新功能稍微困难一些。 (没有ID的数据类型在两年内发生了变化,无论如何 - 它肯定会在某个时刻发生,但据我所知,目前投资回报率为负值。)
您可以像您自己建议的那样选择GUID。然后,您不必担心将人员ID“42”传递给DeleteCar()并意外删除ID为42的汽车.GUID是唯一的;如果由于编程错误而在您的代码中将人GUID传递给DeleteCar,则该GUID将不是数据库中任何汽车的PK。
你可以创建一个简单的 Id
可以帮助区分两者之间的代码的类:
public class Id<T>
{
private int RawValue
{
get;
set;
}
public Id(int value)
{
this.RawValue = value;
}
public static explicit operator int (Id<T> id) { return id.RawValue; }
// this cast is optional and can be excluded for further strictness
public static implicit operator Id<T> (int value) { return new Id(value); }
}
像这样使用:
class SomeClass
{
public Id<Person> PersonId { get; set; }
public Id<Car> CarId { get; set; }
}
假设您的值只能从数据库中检索,除非您将值显式地转换为整数,否则无法在彼此的位置使用这两个值。
在这种情况下,我没有看到自定义检查有多大价值。您可能希望加强测试套件以检查是否发生了两件事情:
- 您的数据访问代码始终按预期工作(即,您没有将不一致的密钥信息加载到您的类中并因此而被滥用)。
- 您的“往返”代码正在按预期工作(即,加载记录,进行更改并将其保存回来并不会以某种方式破坏您的业务逻辑对象)。
拥有一个您可以信任的数据访问(和业务逻辑)层对于解决在尝试实现实际业务需求时遇到的更大图片问题至关重要。如果您的数据层不可靠,那么当您将负载放在子系统上时,您将花费大量精力跟踪(或更糟糕的是,解决)该级别的问题。
相反,如果您的数据访问代码在使用不当(您的测试套件应该向您证明)时是健壮的,那么您可以在更高级别放松一点并相信它们会抛出异常(或者您正在处理它)被虐待时
您听到人们建议使用ORM的原因是这些工具中的许多问题都是以可靠的方式处理的。如果您的实现远远不够,那么这样的转换会很痛苦,请记住,如果您真的希望能够信任,那么您的低级数据访问层需要像良好的ORM一样健壮(因此忘记了在一定程度上)您的数据访问。
而不是自定义验证,您的测试套件可以注入代码(通过依赖注入),在测试运行时对密钥执行强大的测试(命中数据库以验证每个更改)并注入因性能原因而忽略或限制此类测试的生产代码。您的数据层将在失败的密钥上抛出错误(如果您在那里正确设置了外键),那么您也应该能够处理这些异常。
我的直觉说这不值得麻烦。我的第一个问题是你是否确实发现了错误的int被传递的错误(在你的例子中是Car ID而不是Person ID)。如果是这样,可能更糟糕的是整体架构更糟糕,因为你的Domain对象有太多的耦合,并且在方法参数中传递了太多的参数而不是对内部变量起作用。