我在使用sql server作为后端的C#编写的数据库应用程序上工作。为了数据完整性,我尝试在数据库级别上尽可能地强制执行 - 关系,检查约束,触发器。
由于它们,如果数据不一致,则save / update / insert可能会失败,app会抛出SqlException。
我在UI中进行各种验证(如果输入的数据无效,则向用户显示有意义的信息),也在BL中,将错误报告给用户界面的UI。
但是,有些东西确实无法在应用程序中检查,并由db处理:我的意思是当没有级联删除和用户尝试从主表中删除实体等时删除错误
E.g。员工表在很多关系中充当主人 - 员工经理,部门经理,收银员,团队负责人,团队成员等等。如果我添加一个没有涉及任何关系的新员工我可以删除它,但是用户尝试删除一个主要的关系,由于在DB级强制执行RI规则,删除失败(因为它应该),这没关系。
我在try ... catch中编写删除代码并处理异常,告诉用户他无法删除该员工。但我想给用户更有意义的信息 - 记录无法删除的原因。也许这只是一个测试员工记录,也被添加到测试团队。但是用户忘记了添加的内容,如果我能说“无法删除员工,因为它是团队T1的一部分”,用户将知道先去T1团队,删除用户然后再尝试删除它。这是一个简单的例子,因为我说员工可以参与很多关系 - 在我的应用程序中我至少有20个。
解决方案是显示SqlException报告的消息,但这根本不优雅。首先,msg非常技术性 - 它谈论FK,PK,触发器,这对用户来说毫无意义并且会吓到它们。其次,我的应用程序使用多语言UI,所有菜单和消息都以用户选择的语言显示(在登录时或用户配置文件中选择)。并且来自SqlException的消息是英语(如果我使用英语版本)或最差的,不太常见的语言,如德语或荷兰语,如果它发生sql server就是那种语言。
是否有任何通用或推荐的方法从sql异常中提取有意义的信息,以便能够向用户呈现有意义的消息(例如,什么关系或子表导致失败,或什么触发等)。但我可以在程序中以独立于lang的方式测试,然后以用户友好的方式格式化我自己的错误消息?
你如何处理这种情况?
感谢所有答案
(PS:很抱歉长篇文章)
答案 0 :(得分:6)
不幸的是,这里没有一个简单的答案。
所涉及的工作量取决于来自业务层的错误消息的一致性。您需要从“技术”错误消息到面向用户的消息进行某种形式的翻译。
这应该是从您的错误消息到资源键进行某些形式的查找,这可以用来提取特定于语言的错误消息。但是,如果您需要解析消息以获取更多信息(例如:表名等),那么它会变得有点棘手。在这种情况下,您可能需要将错误消息映射到某种形式的正则表达式/处理器以及新的资源字符串。然后,您可以使用从原始错误中提取的信息格式化用户的字符串,并将其呈现给用户。
答案 1 :(得分:3)
好吧,从数据库中,您只能获得这些技术信息,例如“违反外键关系FK_something_to_another”等。
通常,在SqlException中,您还会收到SQL错误代码或其他内容。
最好的方法可能是在数据库中有一个单独的表,它基本上映射那些可能发生在更有意义的,面向用户的消息上的技术SQL错误。例如。如果您的SQL错误出现类似“fk violation blablabaal”的内容,您可以在“UserErrorTable”中输入一个条目,该条目将此映射到用户消息“无法删除用户(this.and.that)”,很可能是因为...... ..(他仍然是团队成员)“或其他什么。
然后,您可以尝试在业务层中捕获这些SqlExceptions,将这些技术信息转换为用户的自定义异常,放入用户友好的消息,并将技术异常粘贴到自定义异常类型的.InnerException中:
public class UserFriendlyException : Exception
{
public string UserErrorMessage { get; set; }
public UserFriendlyException(string message, SqlException exc) : base(message, exc)
{
UserErrorMessage = MapTechnicalExecptionToUserMessage(exc);
}
}
马克
答案 2 :(得分:2)
执行此操作的方法是编写存储过程,并使用TRY
和CATCH
。使用RAISERROR
引发您自己的自定义消息,并检查SqlException
中的错误代码。
答案 3 :(得分:2)
简短的回答是“不要”。让错误冒泡到全局错误处理/日志记录。验证和业务规则通常应该排除数据库异常,因此您最好快速失败并且不提交脏数据。交易也有帮助。
答案 4 :(得分:1)
我们通常在项目中编写某种翻译器。我们将SQL异常消息与一些预定义模式进行比较,并向用户显示等效消息
答案 5 :(得分:1)
关于通用技术>用户友好的错误我只能支持已经给出的答案。
但是对于您与雇主的具体示例,我必须鼓励您不要仅仅依赖于SqlException。在尝试删除员工之前,您应该检查一下他/她是否属于任何团队,是经理等。这将极大地提高您的应用程序的可用性。
伪:
Employee e;
try {
IEnumerable<Team> teams = Team.GetTeamsByEmployee(e);
if (teams.Count() > 0) {
throw new Exception("Employee is a part of team "+ String.Join(",", teams.Select(o => o.Name).ToArray());
}
IEnumerable<Employee> managingEmployees = Employee.GetEmployeesImManaging(e);
if (managingEmployees.Count() > 0) {
throw new Exception("Employee is manager for the following employees "+ String.Join(",", managingEmployees.Select(o => o.Name).ToArray());
}
Employee.Delete(e);
} catch (Exception e) {
// write out e
}
答案 6 :(得分:1)
错误发生。当你遇到什么类型或类型的错误或者你如何处理它并不特别重要时,将你的代码打到TRY ... CATCH ...块并写一个通用的错误报告系统就可以了。当你想要(或被要求)写出比这更好的东西时,可能需要认真努力,正如之前的一些帖子中所概述的那样(我也投了赞成票)。
我倾向于将错误归类为可预测或不可预测的错误。如果您可以预期错误和,您希望使用明确的consice消息来处理它,例如您的“删除员工”情况,则必须相应地进行规划和编码。根据他们的定义,你不能为不可预测的错误做这件事 - 通常是TRY / CATCH进来的地方。
根据您的情况,一种方法可能是在删除行之前,通过查询子表来检查删除是否成功。如果不能,您将确切地知道为什么(以及所有子表,而不仅仅是阻止删除的第一个子表),并且可以向用户显示明确的消息。唉,你必须考虑数据是否可以在支票和实际删除之间发生变化 - 这里可能不是问题,但可能是在其他情况下。
基于TRY ... CATCH ...的替代方案是检查catch块中的错误号。如果它是“由于外键而无法删除”,则可以查询子表并生成消息,如果是其他一些意外错误,则必须依赖于通用消息。
(需要注意的是:有时当它遇到错误时,SQL会连续引发两条错误消息[其中一条违反FK约束?]并且在这些情况下,各种ERROR()函数只返回数据第二个也是不太有用的信息。这是非常令人难以置信的加重,但你可以做的不多。)
答案 7 :(得分:1)
错误消息不等于例外。用户应该找到信息性且最重要的可操作的错误消息。我建议您阅读User Experience Guidelines中有关于错误消息的一些指导原则。 Apple在Writing Good Alert Messages中也有很好的通用指南。
您会立即注意到,大多数(如果不是全部)SQL错误都是不良好的最终用户消息。 '约束FKXF#455违规' - 最终用户的错误。 '文件组已满' - 最终用户的错误。 '死锁' - 同样的。好的应用程序做的是将用户的角色分开。管理员需要查看这些错误,而不是最终用户。因此,应用程序始终记录包含所有详细信息的完整SQL错误,最终通知管理员,然后向用户显示不同的错误,例如“发生系统错误,管理员已收到通知”。
如果最终用户可以操作SQL错误,那么您可以向他显示一条错误消息,指示他如何解决问题(例如,更改输入中的发票日期以满足约束条件)。但即使在这种情况下,大多数情况下你都不应该直接向用户显示SQL错误(你已经看到了一个很好的理由,为什么不:本地化)。我知道这会为您的开发团队带来更大的工作量。您必须知道在每种情况下用户可以操作的错误,从您可能遇到的大量错误中。这是众所周知的,这就是为什么优秀的程序管理员知道大约80%的代码正在处理错误情况,以及为什么“完成”的应用程序通常意味着完成了20%。这就是伟大的应用程序与普通应用程序的不同之处:当事情发生错误时,他们的行为方式。
我的建议是启动渐进式披露的原则。显示声明“操作失败”的一般错误消息。如果用户按下错误消息对话框中的“显示详细信息...”按钮,则显示更多详细信息,并显示SqlError集合(顺便说一句,您应该始终日志并显示整个 SqlException.Errors的SqlError集合,而不是SqlException)。使用SqlError.Number属性在catch块中添加逻辑,决定用户是否可以对错误做任何事情(确定错误是否可操作)并添加适当的信息。
不幸的是没有精灵尘埃。您正在触摸可能是项目中最困难部分的足部。
答案 8 :(得分:0)
总之,我会警告不要依赖SQL异常和错误。对我来说,如果我们依赖这些错误,我们就会遇到麻烦。这种错误消息通常不是用户可读的。此外,UI开发人员可能会说&#34;哦,它会被数据库人员捕获,我不需要验证&#34;。完全错误!
或许更好的方法是确保验证首先防止这些问题。例如如果你不能删除对象A,因为它被对象B引用,那么我们应该在高于数据库的级别实现某种依赖服务。这取决于它是什么类型的应用程序。
另一个例子是字符串长度验证。我们是否真的希望依靠数据库验证来检查用户输入的字段长度。当然,这是一个极端的案例!
通常,如果数据库层抛出异常,则可能是其他地方出现错误的迹象。通过客户端/服务器设置,我们可以断言验证是两者的责任。如果它进入数据库,那么通常为时已晚。
您可能希望使用RAISEERROR,因此可以使用存储过程中的异常来提供调用代码。从那以后,您可以提供合理的错误消息。
从来没有一种适合所有人的方法。我的座右铭是预防胜于治疗!先验证而不是稍后验证。