1

我有一个抽象的、通用的存储库类,它封装了很多逻辑,它能够做到这一点,因为它知道如何隔离唯一的数据行,这要归功于它的KeyCompare属性,该属性被注入到存储库的构造函数中:

Func<T, T, bool> KeyCompare { get; }

此属性的作用是告诉基类使用哪些字段来获取唯一记录 - 有时它只是“Id”,并且可以像这样分配 KeyCompare(传递给构造函数):

(item1, item2) => item1.Id == item2.Id

但是,它也让我可以灵活地使用 Linq to SQL 类,这些类的键跨多个列,如下所示:

(item1, item2) => item1.Field1 == item2.Field1 && item1.Field2 == item2.Field2

这在单元测试中效果很好,但这只是因为我正在使用一个使用 Linq to Object 的模拟仓库进行测试

生产代码使用 Linq to SQL,因为我被告知我的KeyCompare属性不会按预期运行(因为Func不会被转换为 SQL),所以我应该使用 anExpression来代替。所以我开始四处搜索(显然Func不会被翻译成 SQL),看到了很多例子,但没有像我正在做的事情(这是什么迹象吗?)。

我使用的方式Func如下:

public T Item(T item)
{
    return (_items.SingleOrDefault(e => KeyCompare(e, item));
}

我被告知要实现这样的方法(KeyCompare作为一个Expression):

public T Item(T item)
{
    return (_items.SingleOrDefault(KeyCompare);
}

但这不会编译(.SingleOrDefault 想要 a Func,而不是 a Expression),如果它编译了,它就不会使用我需要给它的item值来做我想做的事情。

Func用 Ninject 将它注入到 repo 的构造函数中;这是我绑定KeyCompare构造函数参数的方式:

.WithConstructorArgument("keyCompare", (Func<MockEntity, MockEntity, bool>)((item1, item2) => item1.Id == item2.Id));

所以我被困住了。如何使用 Linq to SQL 进行这项工作

另一种方法是,如果我在基类中删除此KeyCompare属性,则每次我需要实现一个具体的存储库时都编写冗余代码,我想避免这种情况。有了这个KeyCompare属性,我只需要编写特定于每个存储库类型的代码来实现一个新的,我喜欢这样。

这篇文章有我正在谈论的抽象存储库类的完整 [原始 - 它已经改变] 代码:Ninject 可以使用匿名委托(func)作为 ConstructorArgument 吗?

编辑

LINQ-to-SQL : Convert Func<T, T, bool> to an Expression<Func<T, T, bool>>有一个有趣的答案和一个精确的标题(我真的想知道我怎么会错过那个我的研究)。它实际上以一个出色、详细的答案回答了这个问题,我稍后会尝试。

现在的用法有点奇怪,因为这是一个 curried lambda。而不是传递 (x,y) => ... 你将传递 x => y => ...。

我确信该解决方案有效(它非常聪明),但它没有解释为什么传递 (x,y) 需要后空翻和三轴来执行。两个输入有什么关系?如果 Linq to Objects 可以做到这一点,那么究竟是什么阻止了 Linq to SQL 做同样的事情呢?

4

1 回答 1

0

我通过了所有测试——包括 LinqToObject“模拟”存储库和 LinqToSql“生产”存储库(后者是自动集成测试)。就是这样:

从这篇文章的答案:LINQ-to-SQL : Convert Func<T, T, bool> to an Expression<Func<T, T, bool>>,获取代码以创建PartialApplier静态类,其作用是重写动态表达(根据我的掌握)。

然后我制作了两者ListRepositoryDataRepositoryRepositoryBasewhich implements派生IRepository<T>。这是回购类:

public abstract class RepositoryBase<T> : IRepository<T>
    where T : class
{
    public abstract IQueryable<T> Select();
    public abstract void Save(T item);
    public abstract void Delete(T item);
    public abstract void Delete(IEnumerable<T> items);

    public virtual Expression<Func<T, T, bool>> KeyCompare { get; private set; }
    public virtual int Count { get { return Select().Count(); } }

    protected RepositoryBase(Expression<Func<T, T, bool>> keyCompare)
    {
        KeyCompare = keyCompare;
    }

    public virtual T Item(T item)
    {
        var applied = PartialApplier.PartialApply(KeyCompare, item);
        var result = (Select().SingleOrDefault(applied));

        return result;
    }

    /// <summary>
    /// A method that updates the non-key fields of an existing entity with those of specified <see cref="item"/>.
    /// Called by the <see cref="Save"/> method.
    /// </summary>
    /// <param name="existing">The existing record to update.</param>
    /// <param name="item">A <see cref="T"/> object containing the values to update <see cref="existing"/> object with.</param>
    /// <returns></returns>
    protected abstract void UpdateExisting(T existing, T item);

    /// <summary>
    /// A method that saves all specified items.
    /// </summary>
    /// <param name="items">The entities to be saved.</param>
    public virtual void Save(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Save(item);
        }
    }
}

没有问题,ListRepository但需要一些返工才能正确派生RepositoryBase

/// <summary>
/// A non-persistent repository implementation for mocking the repository layer.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ListRepository<T> : RepositoryBase<T>
    where T : class
{
    private readonly List<T> _items = new List<T>();

    public ListRepository(IEnumerable<T> items, Expression<Func<T, T, bool>> keyCompare)
        : base(keyCompare)
    {
        _items.AddRange(items);
    }

    public override IQueryable<T> Select()
    { 
        return _items.AsQueryable(); 
    }

    public override void Save(T item)
    {
        var existing = Item(item);
        if (existing != null)
        {
            _items.Remove(item);
        }

        _items.Add(item);
    }

    public override void Save(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Save(item);
        }
    }

    public override void Delete(T item)
    {
        _items.Remove(item);
    }

    public override void Delete(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Delete(Item(item));
        }
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="existing"></param>
    /// <param name="item"></param>
    protected override void UpdateExisting(T existing, T item)
    {
        throw new NotImplementedException(); // list repo just wipes existing data
    }
}

然后DataRepository可能看起来像这样:

public abstract class DataRepository<T> : RepositoryBase<T>
    where T : class
{
    private DataContext _context;

    public DataRepository(DataContext context, Expression<Func<T, T, bool>> keyCompare)
        : base(keyCompare)
    {
        _context = context;
    }

    public override IQueryable<T> Select()
    {
        return _context.GetTable<T>().AsQueryable();
    }

    /// <summary>
    /// A method that updates an existing item or creates a new one, as needed.
    /// </summary>
    /// <param name="item">The entity containing the values to be saved.</param>
    public override void Save(T item)
    {
        var existing = Item(item);
        if (existing != null)
        {
            UpdateExisting(existing, item);
        }
        else
        {
            _context.GetTable<T>().InsertOnSubmit(item);
        }

        _context.SubmitChanges();
    }

    /// <summary>
    /// A method that deletes specified item.
    /// </summary>
    /// <param name="item"></param>
    public override void Delete(T item)
    {
        var existing = Item(item);
        if (existing != null)
        {
            _context.GetTable<T>().DeleteOnSubmit(existing);
        }

        _context.SubmitChanges();
    }

    public override void Delete(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Delete(item);
        }
    }

    protected override void UpdateExisting(T existing, T item)
    {
        throw new NotImplementedException(); // must override in derived classes - not obvious I know
    }
}

因此,给定一个只有一个简单对象(DataRepositoryIntegrationTest在本例中称为)的 DataContext,我们可以创建一个实现来测试它:

public class TestRepository : DataRepository<DataRepositoryIntegrationTest>
{
    public TestRepository(DataContext context)
        : base(context,  (item1, item2) => item1.Id == item2.Id)
    { }

    protected override void UpdateExisting(DataRepositoryIntegrationTest existing, DataRepositoryIntegrationTest item)
    {
        existing.Value = item.Value;
        existing.DateUpdated = DateTime.Now;
    }
}

然后可以编写测试:

[TestClass]
public class DataRepositoryTests
{
    private IRepository<DataRepositoryIntegrationTest> _repo;
    private static DataContext _context;

    [ClassCleanup]
    public static void CleanUp()
    {
        _context.Dispose();
    }

    public DataRepositoryTests()
    {
        _context = new DataContext(Settings.Default.IntegrationTestConnectionString);
    }

    [TestInitialize]
    public void InitRepository()
    {
        _context.ExecuteCommand("truncate table dbo.IntegrationTest");
        _context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test1', getdate())");
        _context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test2', getdate())");
        _context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test3', getdate())");
        _repo = new TestRepository(_context);
    }

    private DataRepositoryIntegrationTest CreateItem(string value)
    {
        return new DataRepositoryIntegrationTest { Value = value, DateInserted = DateTime.Now };
    }

    [TestMethod]
    public void ShouldInsertItem()
    {
        // arrange
        var item = CreateItem("Test.ShouldInsert1");
        var expectedCount = _repo.Count + 1;

        // act
        _repo.Save(item);
        var actualCount = _repo.Count;

        // assert
        Assert.AreEqual(expectedCount, actualCount);
    }

    [TestMethod]
    public void ShouldInsertItems()
    {
        // arrange
        var count = _repo.Count;
        var items = new DataRepositoryIntegrationTest[] { 
            CreateItem("Test.ShouldInsert1"),
            CreateItem("Test.ShouldInsert2")};
        var expected = count + items.Length;

        // act
        _repo.Save(items);
        var actual = _repo.Count;

        // assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void ShouldUpdateItem()
    {
        if (_repo.Count == 0) throw new AssertInconclusiveException("ShouldUpdateItem requires an existing item.");

        // arrange
        var item = _repo.Item(new DataRepositoryIntegrationTest { Id = 1 });
        var expected = "updated";
        item.Value = expected;

        // act
        _repo.Save(item);
        var result = _repo.Item(new DataRepositoryIntegrationTest { Id = item.Id });
        var actual = result.Value;

        // assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void ShouldDeleteItem()
    {
        if (_repo.Count == 0) throw new AssertInconclusiveException("ShouldUpdateItem requires an existing item.");

        // arrange
        var item = _repo.Item(new DataRepositoryIntegrationTest { Id = 1 });
        var expected = _repo.Count - 1;

        // act
        _repo.Delete(item);
        var actual = _repo.Count;

        // assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void ShouldDeleteItems()
    {
        if (_repo.Count < 2) throw new AssertInconclusiveException("ShouldUpdateItem requires 2 existing items.");

        // arrange
        var items = new DataRepositoryIntegrationTest[] {
            new DataRepositoryIntegrationTest { Id = 1 },
            new DataRepositoryIntegrationTest { Id = 2 }};
        var expected = _repo.Count - items.Count();

        // act
        _repo.Delete(items);
        var actual = _repo.Count;

        // assert
        Assert.AreEqual(expected, actual);
    }
}

他们都通过了!

于 2013-04-20T18:48:23.540 回答