IEnumerable仅可枚举一次:xUnit测试两次断言结果不一致原因咨询
这个现象的核心原因有两个:LINQ的延迟执行特性,加上你在查询中修改了原始实体的属性,导致后续枚举时查询条件不匹配。
1. 延迟执行的LINQ查询
你的SetFilePath2方法返回的是一个未具体化的IEnumerable<T>——它本质上是一个查询计划,而不是实际的数据集合。每次你调用Count()(或者用foreach枚举)时,都会重新执行整个Join + Select的流程,而不是复用第一次的结果。
2. 第一次枚举时修改了实体的File属性
看你Select里的代码:
x.entity.File = x.blob.Uri.AbsoluteUri; return x.entity;
第一次调用Count()的时候,这段代码会被执行:你把原始Photo对象的File属性从原来的"1.jpg"改成了完整的Blob URL(比如"https://blabla.net/media/photos/1.jpg")。
当第二次调用Count()时,LINQ会重新执行Join操作,此时entities里的Photo对象的File已经是修改后的URL了,而blobs的y.Name.Substring(prefixLength)还是"1.jpg"/"2.jpg"——两者完全不匹配,所以Join返回空集合,Count()自然就是0,导致断言失败。
另外还有个小细节:你的测试代码里blobRepository是null,不过因为SetFilePath2是扩展方法,C#允许对null实例调用扩展方法(扩展方法本质是静态方法,第一个参数只是语法糖),所以这部分不会报错,但属于不规范的写法。
要解决这个问题,你需要在方法内部把查询结果具体化,确保只执行一次LINQ查询,并且后续枚举的是同一个集合:
修改SetFilePath2方法,在返回前用ToList()或者ToArray()将查询结果固化:
public static IEnumerable<T> SetFilePath2<T>(this IBlobRepository blobRepository, IEnumerable<T> entities, IEnumerable<CloudBlockBlob> blobs) where T : BlobEntityBase { var firstBlob = blobs.FirstOrDefault(); if (firstBlob is not null) { var prefixLength = firstBlob.Parent.Prefix.Length; return entities .Join(blobs, x => x.File, y => y.Name.Substring(prefixLength), (entity, blob) => (entity, blob)) .Select(x => { x.entity.File = x.blob.Uri.AbsoluteUri; return x.entity; }) .ToList(); // 在这里具体化集合,避免重复执行查询 } else { return Enumerable.Empty<T>(); } }
这样,SetFilePath2返回的是一个List<T>,它是具体化的集合,多次枚举都会返回相同的结果,不会重新执行查询,也就不会出现第二次Count()为0的情况了。
另外,你的测试里两次写了完全一样的断言,其实可以改成更有意义的验证,比如检查File属性是否被正确设置:
// Assert Assert.Equal(2, photos.Count()); Assert.All(photos, p => Assert.StartsWith("https://blabla.net/media/photos/", p.File));
内容的提问来源于stack exchange,提问作者VSDekar

