【问题标题】:FluentAssertions Should().BeEquivalentTo doesn't compare run-time derived types on EF dynamic proxiesFluentAssertions Should().BeEquivalentTo 不比较 EF 动态代理上的运行时派生类型
【发布时间】:2018-08-25 22:19:31
【问题描述】:

我正在使用 FluentAssertions 使用 Should().BeEquivalentTo() 比较两个对象,其中一个对象是 EF 动态代理。但是,5.0.0 中ShouldBeEquivalentToShouldAllBeEquivalentTo (#593) 的统一似乎破坏了使用RespectingRuntimeTypes 时的功能。除非我为对象图中的每个类型显式添加ComparingByMembers,否则不再比较已声明类型的派生类型的属性成员。有没有办法使用其他设置解决这个问题?

【问题讨论】:

  • 这些类型会覆盖“Equals”吗?
  • @DennisDoomen No.
  • 你读过这个吗? continuousimprover.com/2018/02/…
  • @DennisDomen 我确实这样做了,我阅读了“迈向统一 API”,然后直接跳到“升级提示”(将其视为“TLDR”部分),期望这会突出任何突破变化。我今天早上发现基类确实错误地实现了Equals,删除它可以解决问题。感谢您的回复,但我建议您在“升级提示”部分(在“对 BeEquivalentTo 的更改”下)添加一个额外的项目符号,以确保任何 Equals 实现符合它应该的引用类型值语义原则到?
  • 好主意。将此添加到原始博客中。

标签: c# entity-framework dynamic-proxy fluent-assertions


【解决方案1】:

我已经编写了以下扩展方法来尝试解决该问题,但仅在运行时在动态代理上解决派生类型的问题似乎很麻烦:

public static class FluentAssertionsExtensions
{
    /// <summary>
    /// Extends the functionality of <see cref="EquivalencyAssertionOptions{TExpectation}" />.ComparingByMembers by recursing into the entire object graph
    /// of the T or passed object and marks all property reference types as types that should be compared by its members even though it may override the
    /// System.Object.Equals(System.Object) method. T should be used in conjunction with RespectingDeclaredTypes. The passed object should be used in
    /// conjunction with RespectingRuntimeTypes.
    /// </summary>
    public static EquivalencyAssertionOptions<T> ComparingByMembersRecursive<T>(this EquivalencyAssertionOptions<T> options, object obj = null)
    {
        var handledTypes = new HashSet<Type>();
        var items = new Stack<(object obj, Type type)>(new[] { (obj, obj?.GetType() ?? typeof(T)) });

        while (items.Any())
        {
            (object obj, Type type) item = items.Pop();
            Type type = item.obj?.GetType() ?? item.type;

            if (!handledTypes.Contains(type))
            {
                handledTypes.Add(type);

                foreach (PropertyInfo pi in type.GetProperties())
                {
                    object nextObject = item.obj != null ? pi.GetValue(item.obj) : null;
                    Type nextType = nextObject?.GetType() ?? pi.PropertyType;

                    // Skip string as it is essentially an array of chars, and needn't be processed.
                    if (nextType != typeof(string))
                    {
                        if (nextType.GetInterface(nameof(IEnumerable)) != null)
                        {
                            nextType = nextType.HasElementType ? nextType.GetElementType() : nextType.GetGenericArguments().First();

                            if (nextObject != null)
                            {
                                // Look at all objects in a collection in case any derive from the collection element type.
                                foreach (object enumObj in (IEnumerable)nextObject)
                                {
                                    items.Push((enumObj, nextType));
                                }

                                continue;
                            }
                        }

                        items.Push((nextObject, nextType));
                    }
                }

                if (type.IsClass && type != typeof(string))
                {
                    // ReSharper disable once PossibleNullReferenceException
                    options = (EquivalencyAssertionOptions<T>)options
                        .GetType()
                        .GetMethod(nameof(EquivalencyAssertionOptions<T>.ComparingByMembers))
                        .MakeGenericMethod(type).Invoke(options, null);
                }
            }
        }

        return options;
    }
}

应该这样调用:

foo.Should().BeEquivalentTo(bar, o => o
    .RespectingRuntimeTypes()
    .ComparingByMembersRecursive(foo)
    .ExcludingMissingMembers());

【讨论】:

    【解决方案2】:

    我最近使用Microsoft.EntityFrameworkCore.Proxies 遇到了同样的问题。就我而言,我必须比较持久属性,而忽略比较其余的甚至导航属性。

    解决方案是实现接口FluentAssertions.Equivalency.IMemberSelectionRule 以排除不必要的属性。

    public class PersistentPropertiesSelectionRule<TEntity> : IMemberSelectionRule 
        where TEntity : class
    {
        public PersistentPropertiesSelectionRule(DbContext dbContext) => 
            this.dbContext = dbContext;
    
        public bool IncludesMembers => false;
    
        public IEnumerable<SelectedMemberInfo> SelectMembers(
            IEnumerable<SelectedMemberInfo> selectedMembers, 
            IMemberInfo context, 
            IEquivalencyAssertionOptions config)
        {
            var dbPropertyNames = dbContext.Model
                .FindEntityType(typeof(TEntity))
                .GetProperties()
                .Select(p => p.Name)
                .ToArray();
    
            return selectedMembers.Where(x => dbPropertyNames.Contains(x.Name));
        }
    
        public override string ToString() => "Include only persistent properties";
    
        readonly DbContext dbContext;
    }
    

    然后编写一个扩展方法可以帮助方便使用并提高可读性。扩展方法可以是下面这段代码。

    public static class FluentAssertionExtensions
    {
        public static EquivalencyAssertionOptions<TEntity> IncludingPersistentProperties<TEntity>(this EquivalencyAssertionOptions<TEntity> options, DbContext dbContext) 
            where TEntity : class
        {
            return options.Using(new PersistentPropertiesSelectionRule<TEntity>(dbContext));
        }
    }
    

    最后,您可以像下面的代码一样调用测试中的扩展方法。

    // Assert something
    using (var context = DbContextFactory.Create())
    {
        var myEntitySet = context.MyEntities.ToArray();
        myEntitySet.Should().BeEquivalentTo(expectedEntities, options => options
            .IncludingPersistentProperties(context)
            .Excluding(r => r.MyPrimaryKey));
    }
    

    这个实现解决了我的问题,代码看起来很整洁。

    【讨论】:

      猜你喜欢
      • 2018-08-09
      • 2019-01-21
      • 2013-04-15
      • 2021-09-02
      • 2020-06-09
      • 2014-11-13
      • 1970-01-01
      • 2022-04-19
      • 2014-11-12
      相关资源
      最近更新 更多