一个关于良好编程风格的疑问?

圣殿骑士18 2019-02-09 10:57:32
有这么一种场景,用户输入一个字符串,根据这个字符串模糊查询出结果。而模糊查询的结果可能会有多个,正常的情形下结果是只有一个。以下是例子。

我是用EF的,不顾这和EF并无主要关系,只是我的代码里用到了EF。实现有两种方式,一种是先查询所有结果,然后在 内存中进行判断

items = context.ass_ba_item.Where(c => c.EntId == userModel.EntId && (c.ItemCode.EndsWith(inputCode) || c.ItemBarCode.EndsWith(inputCode))).ToList();
if (items.Count > 1) throw new MessageErrorException("不是唯一的资产编码,请输入精确的资产编码!");
var item = items.FirstOrDefault();
if (item == null) throw new MessageErrorException(string.Format("没有找到此编码的资产信息: {0}", inputCode));

代码中可以看到,先ToList,然后内存中做判断,和取第一行的值,保证结果记录有且只有一行。
但这种方式有个问题:边界条件不可控。即如果输入一个很短的编码,导致查出一堆数据,要将数据从数据库加载,这会导致性能的问题。当然我们也知道,这种边界条件属于可能会发生,但极少出现的情况,当然也有可能有人故意利用这个漏洞进行攻击,造成性能的大幅下降(如果这个程序发布到了互联网上)。

还有一种方式,是稳健的方式。不要先ToList,Count判断和取行操作,分两次请求数据库。

items = context.ass_ba_item.Where(c => c.EntId == userModel.EntId && (c.ItemCode.EndsWith(inputCode) || c.ItemBarCode.EndsWith(inputCode)));
if (items.Count() > 1) throw new MessageErrorException("不是唯一的资产编码,请输入精确的资产编码!");
var item = items.FirstOrDefault();
if (item == null) throw new MessageErrorException(string.Format("没有找到此编码的资产信息: {0}", inputCode));


我的判断是,如果从正常的操作看,99%的操作都会正确的取到第一条记录,那么反正要取第一条,我一次就取出来,然后进行内存处理,数据库压力更低,数据传输带宽低,性能更好。但它的问题也明显:边界条件控制不严。
而后者,Count和FirstOrDefault两次请求,数据库性能稍差,但边界控制严谨。

那么问题来了,如果论良好的编程方式,应该是哪一种?边界条件控制是否应排在较高的考虑位置?
...全文
2802 40 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
40 条回复
切换为时间正序
请发表友善的回复…
发表回复
正怒月神 2019-02-13
  • 打赏
  • 举报
回复
其实说白了, firstordefault 是 select top 1 SingleOrDefault 是 select top 2 具体使用哪种,还是要看业务逻辑。
程序员的键盘 2019-02-13
  • 打赏
  • 举报
回复
对于这里的业务来说,我的关注点好像在throw,这里操作的对象应该是用户吧,那为什么是在程序内抛出错误给程序员看到,而不是返回友好信息通知用户呢,难道做出输入的不是用户吗.

还有“边界条件不可控。即如果输入一个很短的编码”,是否可以考虑在客户端输入时限制至少需要输入多少个字符.

以上只是疑问,并不是意见,本人一概不承担任何后果
  • 打赏
  • 举报
回复
咋回事?。。。。。。。。。
wanghui0380 2019-02-12
  • 打赏
  • 举报
回复
所以你这个问题的本质并非“良好编程风格”也非“EF”
而是如何使用“断言”保证业务逻辑,你自己写的那些扩展其实都可以拿掉,他不是项目普适的,你写的其实是“场合性业务断言保证”
wanghui0380 2019-02-12
  • 打赏
  • 举报
回复
我个人选择直接firstordefault,因为从开始编写代码的时候,一直养成的习惯都是“断言”在前的方式。

至于你说的count问题,这个并不能怪罪什么“bug”,那不是bug,那是业务问题。你数据库写入都不保证唯一的,那么你怪罪读取业务读的不是你想要的“唯一”么,很明显你不能怪罪读取的,只能说业务上需要按规则提取一个罢了
小魔人 2019-02-12
  • 打赏
  • 举报
回复
实用性.................
老马历写记 2019-02-12
  • 打赏
  • 举报
回复
涉及数据库访问的,通常准则就是,少访问数据库,尽可能将运算、逻辑处理交由应用去处理。
BlackKey 2019-02-12
  • 打赏
  • 举报
回复
有用啊。。。。。。。。。。。。。。
  • 打赏
  • 举报
回复
引用 36 楼 圣殿骑士18 的回复:
针对我最后提出的问题,说一下我的习惯用法: 原则和前提: 1、因为垃圾数据造成的问题,不需要反馈到前台,但能发现这种异常的话最好。 2、正常业务需求中,有必要对用户的输入进行完善的验证和异常提示,但在EF的取数据的方法中,默认的异常反馈到前台的话不够友好,需要文本定制。 1. FirstOrDefault 数据都正常的情况下,一个查询(比如用户输入的查询),有可能取不到数据,如果有则必定只取到一条数据时。此时可以用FirstOrDefault。 2. SingleOrDefault 以上 1 的情况,如果用SingleOrDefault,会更安全。它的特点: * 语义上最准确。因为能取到数据时必定只取到一条,所以语义上应该是Single,而不是First * 能防止垃圾数据的干扰。如果有垃圾数据存在,本来应该只有一条数据,但实际上取出两条,那么使用Single能够及时发现垃圾数据对系统的影响。否则如果用First,有50%的概率刚好取到垃圾数据。 * SingleOrDefault性能比FirstOrDefault稍差,因为它取两条数据,而FirstOrDefault取一条 3. First 数据都正常的情况下,一个查询(比如代码写定的按唯一Id的查询),必定能取到一条数据时。此时可以用First。 3.Single 使用Single之于First,与SingleOrDefault之于FirstOrDefault类似。语义更精确,防垃圾数据,性能稍差。 3.Take 我当前帖子的应用场景就是,用户查询条件是输入的,因此可能查询到0个,1个,多个结果,只有查询到1个结果时才是正确的输入。同时非1个结果时,都要提示用户。虽然使用Single原则上没问题,但系统的提示不够友好。所以我使用Take(2)后,自己判断结果集并抛出相应异常文本。 如果按语义理解,我其实挺疑惑,用First或者用FirstOrDefault的应用场景应该几乎很少。多数的业务场景应该是Single,SingleOrDefault,Take。但我估计,用FirstOrDefault的人最多,不知道为什么,可能是字母F排在靠前,所以大家会先用它。但用它之后可能会导致的问题是: 1. 因为用户输入的查询条件问题,导致实际查询出多条,但只取第一条,取出了错误的数据。 2. 程序员知道应该做后续校验,因此自己使用了FirstOrDefault之后,在另外写校验代码,这就造成了代码冗余。用Single代替更好。
first要排序的,大数据下single性能优于frist,其他都是你说的没错。
圣殿骑士18 2019-02-12
  • 打赏
  • 举报
回复
针对我最后提出的问题,说一下我的习惯用法:

原则和前提:
1、因为垃圾数据造成的问题,不需要反馈到前台,但能发现这种异常的话最好。
2、正常业务需求中,有必要对用户的输入进行完善的验证和异常提示,但在EF的取数据的方法中,默认的异常反馈到前台的话不够友好,需要文本定制。

1. FirstOrDefault
数据都正常的情况下,一个查询(比如用户输入的查询),有可能取不到数据,如果有则必定只取到一条数据时。此时可以用FirstOrDefault。

2. SingleOrDefault
以上 1 的情况,如果用SingleOrDefault,会更安全。它的特点:
* 语义上最准确。因为能取到数据时必定只取到一条,所以语义上应该是Single,而不是First
* 能防止垃圾数据的干扰。如果有垃圾数据存在,本来应该只有一条数据,但实际上取出两条,那么使用Single能够及时发现垃圾数据对系统的影响。否则如果用First,有50%的概率刚好取到垃圾数据。
* SingleOrDefault性能比FirstOrDefault稍差,因为它取两条数据,而FirstOrDefault取一条

3. First
数据都正常的情况下,一个查询(比如代码写定的按唯一Id的查询),必定能取到一条数据时。此时可以用First。

3.Single
使用Single之于First,与SingleOrDefault之于FirstOrDefault类似。语义更精确,防垃圾数据,性能稍差。

3.Take
我当前帖子的应用场景就是,用户查询条件是输入的,因此可能查询到0个,1个,多个结果,只有查询到1个结果时才是正确的输入。同时非1个结果时,都要提示用户。虽然使用Single原则上没问题,但系统的提示不够友好。所以我使用Take(2)后,自己判断结果集并抛出相应异常文本。

如果按语义理解,我其实挺疑惑,用First或者用FirstOrDefault的应用场景应该几乎很少。多数的业务场景应该是Single,SingleOrDefault,Take。但我估计,用FirstOrDefault的人最多,不知道为什么,可能是字母F排在靠前,所以大家会先用它。但用它之后可能会导致的问题是:
1. 因为用户输入的查询条件问题,导致实际查询出多条,但只取第一条,取出了错误的数据。
2. 程序员知道应该做后续校验,因此自己使用了FirstOrDefault之后,在另外写校验代码,这就造成了代码冗余。用Single代替更好。

baidu_41757154 2019-02-12
  • 打赏
  • 举报
回复
厉害了高手学习
良朋 2019-02-12
  • 打赏
  • 举报
回复
P哥牛P啊!
OrdinaryCoder 2019-02-12
  • 打赏
  • 举报
回复
留个言,现在不太懂,以后用到了回来再看
  • 打赏
  • 举报
回复
  • 打赏
  • 举报
回复
麻烦不麻烦啊,其实没必要这样,抛弃Java就好
带鱼工作室 2019-02-11
  • 打赏
  • 举报
回复
引用 5 楼 以专业开发人员为伍 的回复:
回到基本的 sql 语言概念,你可能没有或者忘记了 select top n ...... 功能,这怎么能行?
有道理
  • 打赏
  • 举报
回复
不错啊!学习学习!
正怒月神 2019-02-11
  • 打赏
  • 举报
回复
到底使用singleordefault还是firstordefault,应该是看业务逻辑了。 我觉得。和强制使用哪个,并没有关系。 至于楼主列举的业务逻辑,用sp的方式搞定就可以了。
旅途中的人 2019-02-11
  • 打赏
  • 举报
回复
我也想知道,等待最佳答案。
  • 打赏
  • 举报
回复
我们这边要求禁止使用take或者frist方法,只能用single。如果中间出错,那是数据健康性问题,数据库查询语句不应该考虑数据健康问题,这种时候抛出异常不做处理。 一开始我很抗拒,但是大家都遵循这个规则后真的会变的很舒适。
加载更多回复(20)

13,190

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 分析与设计
社区管理员
  • 分析与设计社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧