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