C# 比较Linq查询中的字节[]

C# 比较Linq查询中的字节[],c#,unit-testing,mocking,C#,Unit Testing,Mocking,我的SQL表中有一个二进制列,我使用以下C#代码成功地查询了该表: 请注意:“tosha256 hashbytes”是我编写的一个扩展方法,它返回一个字节[] 这非常有效,因为SQL将比较字节[]的内容并返回具有匹配“UrlHash”的记录 然而,这在我的单元测试中不起作用,因为比较是在内存中执行的,比较字节[]的规则显然是不同的。如果两个字节数组位于内存中的同一位置,而不是通过比较数组的内容,C#似乎会认为它们相等 这意味着以下单元测试将失败 var data = new[] {

我的SQL表中有一个二进制列,我使用以下C#代码成功地查询了该表:

  • 请注意:“tosha256 hashbytes”是我编写的一个扩展方法,它返回一个字节[]
这非常有效,因为SQL将比较字节[]的内容并返回具有匹配“UrlHash”的记录

然而,这在我的单元测试中不起作用,因为比较是在内存中执行的,比较字节[]的规则显然是不同的。如果两个字节数组位于内存中的同一位置,而不是通过比较数组的内容,C#似乎会认为它们相等

这意味着以下单元测试将失败

var data = new[]
{
    new LandingPage() { UrlHash = "http://www.whatever.com".ToSHA256HashBytes() },
    new LandingPage() { UrlHash = "http://mycompany.com/another/folder/page.php"".ToSHA256HashBytes() },
    new LandingPage() { UrlHash = "http://someothercompany.com/folder/somepage.html"".ToSHA256HashBytes() }
};
var mockData = new Mock<DbSet<T>>();
var queryableData = data.AsQueryable();
mockData.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryableData.Provider);
mockData.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryableData.Expression);
mockData.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryableData.ElementType);
mockData.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryableData.GetEnumerator());

var mockContext = new Mock<MyContext>();
mockContext.Setup(m => m.LandingPages).Returns(mockData.Object);

var hash = "http://www.whatever.com".ToSHA256HashBytes();
var landingPage = mockContext.Object.LandingPages.FirstOrDefault(lp => lp.UrlHash == hash);
Assert.IsNotNull(landingPage);
var数据=新[]
{
新登录页(){UrlHash=”http://www.whatever.com“.ToSHA256HashBytes()},
新登录页(){UrlHash=”http://mycompany.com/another/folder/page.php“”.ToSHA256HashBytes()},
新登录页(){UrlHash=”http://someothercompany.com/folder/somepage.html“”.ToSHA256HashBytes()}
};
var mockData=new Mock();
var queryableData=data.AsQueryable();
mockData.As().Setup(m=>m.Provider).Returns(queryableData.Provider);
mockData.As().Setup(m=>m.Expression).Returns(queryableData.Expression);
mockData.As().Setup(m=>m.ElementType).Returns(queryableData.ElementType);
mockData.As().Setup(m=>m.GetEnumerator()).Returns(queryableData.GetEnumerator());
var mockContext=new Mock();
Setup(m=>m.LandingPages).Returns(mockData.Object);
变量哈希=”http://www.whatever.com“.tosha256 hashbytes();
var landingPage=mockContext.Object.LandingPages.FirstOrDefault(lp=>lp.UrlHash==hash);
Assert.IsNotNull(登录页面);
是否有一种方法可以编写Linq查询,以便在单元测试和查询数据库时都能正常工作

我发现了一个非常复杂的问题,但OP通过更改他的查询(不幸的是,这对我来说不是一个选项)解决了他的问题,而不是实际找到他原始问题的解决方案。

您可以使用扩展方法:

var landingPage = context.LandingPages
    .FirstOrDefault(lp => lp.UrlHash.SequenceEqual(hash));

SequenceEqual
的返回值为
true
当且仅当两个源序列长度相等,并且根据其类型的默认相等比较器,它们对应的元素相等时。

好的,我花了一些时间,下面是可能对您有所帮助的内容

首先,我创建了定制的
IQueryable
实现(工作方式类似于适配器:简单地将调用转换为
IQueryable
-实例(属性“Origin”),作为构造函数参数传递)。 唯一的区别是对
CreateQuery
Execute
的调用在执行之前进行转换。我们访问表达式树的每个节点,并将所有
Equals(byteArray1,byteArray2)
的节点替换为
Enumerable.SequenceEquals(byteArray1,byteArray2)

首先,这里是使用示例:

var data = new[]
{
    new LandingPage() { UrlHash = "http://www.whatever.com".ToSHA256HashBytes() },
    new LandingPage() { UrlHash = "http://mycompany.com/another/folder/page.php"".ToSHA256HashBytes() },
    new LandingPage() { UrlHash = "http://someothercompany.com/folder/somepage.html"".ToSHA256HashBytes() }
}

var binaryCompareQuery = data
      .AsQueryable()        // Get simple queryable  
      .WithBinaryCompare(); // Use SequentalEquals for byte arrays
实施 下面是
IQueryable
适配器:

public class BinaryCompareQuery<T> : IQueryable<T>, IQueryProvider
{
    private EqualsReplacer Replacer { get; }
    private IQueryable<T> Origin { get; }

    public BinaryCompareQuery(IQueryable<T> origin)
    {
        Replacer = new EqualsReplacer();
    }

    #region IQueryable implementation

    public IEnumerator<T> GetEnumerator()
        => Origin.GetEnumerator();

    public IQueryProvider Provider
        => this;

    public Expression Expression
        => Origin.Expression;

    IEnumerator IEnumerable.GetEnumerator()
        => Origin.GetEnumerator();

    public Type ElementType
        => Origin.ElementType;

    #endregion

    #region IQueryProvider implementation

    IQueryable IQueryProvider.CreateQuery(Expression expression)
        => Origin.Provider.CreateQuery(Replacer.Visit(expression));

    IQueryable<TResult> IQueryProvider.CreateQuery<TResult>(Expression expression)
        => Origin.Provider.CreateQuery<TResult>(Replacer.Visit(expression));

    object IQueryProvider.Execute(Expression expression)
        => Origin.Provider.Execute(Replacer.Visit(expression));

    TResult IQueryProvider.Execute<TResult>(Expression expression)
        => Origin.Provider.Execute<TResult>(Replacer.Visit(expression));

    #endregion
}
附加:扩展方法,允许将我们的行为添加到任何IQueryable中

public static class BinaryCompareQueryExtensions
{
    public static BinaryCompareQuery<T> WithBinaryCompare<T>(this IEnumerable<T> enumerable)
    {
        var queryable = (enumerable as IQueryable<T>) ?? enumerable.AsQueryable();

        return new BinaryCompareQuery<T>(queryable);
    }
}
公共静态类BinaryCompareQueryExtensions
{
公共静态BinaryCompareQuery with BinaryCompare(此IEnumerable可枚举)
{
var queryable=(可枚举为IQueryable)??可枚举.AsQueryable();
返回新的BinaryCompareQuery(可查询);
}
}

就这些;)

不幸的是,这是行不通的。这个想法在我引用的另一个SO问题中提到过,正如在那个问题中提到的:如果我将这个比较重写为UrlHash.SequenceEqual(哈希),那么我的单元测试将通过,但是当我与SQL对话时,我会得到一个异常。(System.NotSupportedException:不支持查询运算符“SequenceEqual”。@desautelsj噢,我错过了。SQL使用什么类型来存储您的
字节[]
UrlHash
?SQL列是二进制的(32)@desautelsj我认为您无法使用LINQ to SQL并在服务器端进行
字节[]
比较,因为在传递给LINQ方法的
表达式
参数中,它只能识别某些函数。您链接到的问题的答案似乎为您仅使用LINQtoSQL的情况提供了解决方案。现在我对您的问题有了更好的理解,我认为不会有一段代码可以用于这两种情况(mocked和linqtosql)。我能想到的唯一(不雅观的)解决方案是将散列存储为字符串。@desautelsj更雅观的解决方案是改进LINQ到SQL转换器,以支持
表达式
s,其中包括调用
Enumerable.SequenceEqual(a,b)
,因为二进制比较是大多数SQL提供程序支持的操作,我相信。这听起来不错。。。但它不起作用:(我已经从ctor修复了null ref设置原点,但无论如何,没有任何内容进入VisitBinary
internal class EqualsReplacer : ExpressionVisitor
{
    // public static bool Enumerable.SequenceEqual<byte>(this IEnumerable<byte> first, IEnumerable<byte> second)
    private static readonly MethodInfo SequenceEqualMethod = typeof(Enumerable)
        .GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Where(x => x.Name == "SequenceEqual")
        .First(x => x.GetParameters().Length == 2)
        .MakeGenericMethod(typeof(byte));

    protected override Expression VisitBinary(BinaryExpression node)
    {
        // Skip all nodes except 'Equal' nodes
        if (node.NodeType != ExpressionType.Equal)
            return base.VisitBinary(node);

        // Skip all 'Equal' nodes with arguments other than byte[]
        if (node.Left.Type != typeof(byte[]) || node.Right.Type != typeof(byte[]))
            return base.VisitBinary(node);

        // Apply rewrite for all inner nodes
        var left = Visit(node.Left);
        var right = Visit(node.Right);

        // Rewrite expression, changing Equals
        return Expression.Call(SequenceEqualMethod, left, right);
    }
}
public static class BinaryCompareQueryExtensions
{
    public static BinaryCompareQuery<T> WithBinaryCompare<T>(this IEnumerable<T> enumerable)
    {
        var queryable = (enumerable as IQueryable<T>) ?? enumerable.AsQueryable();

        return new BinaryCompareQuery<T>(queryable);
    }
}