【问题标题】:How do I make this Func work with Linq to SQL?如何使这个 Func 与 Linq to SQL 一起工作?
【发布时间】:2013-04-21 02:55:19
【问题描述】:

我有一个抽象的、通用的存储库类,它封装了很多逻辑,它能够做到这一点,因为它知道如何隔离唯一的数据行,这要归功于它的 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),所以我应该使用 @987654328 @ 反而。所以我开始四处搜索(显然Func 不会被翻译成 SQL),并看到了很多例子,但与我正在做的事情完全不同(这是某种迹象吗?)。

我使用Func的方式如下:

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

我被告知要实现这样的方法(KeyCompareExpression):

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

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

我正在使用 Ninject 将这个 Func 注入到 repo 的构造函数中;下面是我绑定 KeyCompare 构造函数参数的方法:

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

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

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

这篇文章包含我正在谈论的抽象存储库类的完整 [原始 - 此后已更改] 代码:Can Ninject use an anonymous delegate (func) as a 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 做同样的事情?

【问题讨论】:

  • 如果您使用.AsQueryable() 将查询转换为IQueryable,您可以使用表达式吗?
  • @EricAndres 对,我的ListRepository&lt;T&gt; 使用的是List&lt;T&gt;,所以使用.AsQueryable() 确实有效。但我仍然不能使用这两个T 参数...

标签: c# linq-to-sql func


【解决方案1】:

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

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

然后我将ListRepositoryDataRepository 都派生自实现IRepository&lt;T&gt;RepositoryBase。这是回购类:

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
    }
}

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

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);
    }
}

他们都通过了!

【讨论】:

  • 唯一的痒点(我现在可以看到......方法,因为没有 abstract override 这样的方式来实现抽象方法(有点像接口方法的抽象实现,但对于抽象类方法)......但我可以忍受它。
猜你喜欢
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-01
相关资源
最近更新 更多