我正在研究CRUD测试类,因为在验证我的NHibernate映射时,我厌倦了重复相同的测试模式。
我已经对代码进行了重构,并且已经达到了一切都在按照我设想的方式发挥作用的一点。一切都基于字符串,反射方法使用这些字符串来调用适当的存储库方法,并获取相应属性的值,如实体Id。
这有效,但我确信我不需要为这些事情使用字符串。
所以我开始与Linq合作。我不是一个沉重的Linq用户,下面的代码让我感到非常困惑。
它几乎完美地工作(我几乎可以在一秒钟内完成)并且我很高兴它有效但我真的很想知道为什么。
[Test]
public void TestCanUseTesterWithLinqSecondEffort()
{
IRepositoryTestContextManager contextManager = new NHRepositoryTestContextManager();
contextManager.SetUpRepositoryContextAndSchema();
TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);
contextManager.ResetRepositoryContext();
Client retrievedClient = TestRetrieveMethodWithLinq<NHClientRepository, Client, string>((clientRepository, publicId) => clientRepository.GetClient(publicId), client => client.PublicId, _newClient);
contextManager.TearDownRepositoryContext();
Assert.IsNotNull(retrievedClient);
Assert.AreNotSame(_newClient, retrievedClient);
Assert.AreEqual(_newClient.Id, retrievedClient.Id);
Assert.AreEqual(_newClient.PublicId, retrievedClient.PublicId);
Assert.AreEqual(_newClient.Name, retrievedClient.Name);
}
private void TestInsertMethodWithLinq<TRepositoryType, TEntityType>(Action<TRepositoryType, TEntityType> insertMethod, TEntityType entity)
where TRepositoryType : class, new()
{
insertMethod.Invoke(new TRepositoryType(), entity);
}
private TEntityType TestRetrieveMethodWithLinq<TRepositoryType, TEntityType, TArgumentType>(Func<TRepositoryType, TArgumentType, TEntityType> retrieveMethod, Func<TEntityType, TArgumentType> paramValue, TEntityType theEntity)
where TRepositoryType : class, new()
{
return retrieveMethod.Invoke(new TRepositoryType(), paramValue(theEntity));
}
具体来说,我正在谈论两个被调用的委托(我知道一个人不必调用invoke来使用委托。我把它包括在内以便澄清)。编译器如何转换Linq表达式,以便在新实例化的类上调用正确的方法,在本例中为TRepositoryTypes?
关于几乎完全“几乎完美”,如果在处理调用方法期间存在异常,则吞下异常。不知道为什么会这样,但是我已经可以看到测试未完成的情况,并且由于吞下了异常而错过了问题。
嚼开它。提前谢谢。
答案 0 :(得分:2)
编译器如何转换Linq表达式,以便在新实例化的类上调用正确的方法,在本例中为TRepositoryTypes?
他们实际上不是Linq表达式(Expression<T>
),只是常规的'lambda'。
编译器只是在“匿名”类中创建一个“匿名”方法来捕获任何变量。在这种情况下,您没有捕获的变量 - 所以这个:
TestInsertMethodWithLinq<NHClientRepository, Client>(
(x, y) => x.InsertClient(y), _newClient
);
简单地转换为“匿名”方法:
class __abcde {
public void __fghij(NHClientRepository x, Client y) {
x.InsertClient(y);
}
}
将调用者转换为:
var c = new __abcde();
c.__fghij(new NHClientRepository(), _newClient);
由于您的泛型约束需要new()
no-args构造函数,因此编译器能够插入new NHClientRepository()
位。
答案 1 :(得分:0)
我不是NHibernate用户(或任何其他ORM),所以我只能推测。但我怀疑这里发生的是你正在使用一个闭包。当您创建闭包时,您在lambda表达式中使用的变量将被捕获/关闭/提升到类中以及该方法,以便即使您不调用该值也保持当前状态直到很久以后的方法。
答案 2 :(得分:0)
我可能错过了一些东西,但我相信你指的是泛型和多态性。
您希望传入一个TRepository类型,并调用所有TRepository类型都可以调用的方法。魔术发生在这里:
TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);
您说我将调用此方法并使用NHClientRepository和Client类型。然后进行函数调用,泛型方法将在此行中使用NHClient:
insertMethod.Invoke(new TRepositoryType(), entity);
这会将TRepositoryType()解析为NHClientRepository。然后,该方法使用您传入的类型和操作进行调用,为您提供结果。
您可能希望在测试中设置一个断点,并在观看调用堆栈的同时单步调试,如果您想尝试跟踪发生的事情,您可以查看所有泛型类型和方法调用等来帮助您了解最新情况。
编辑:在参考您的评论时,C#3类型推断正在帮助您完成操作。类型推断是将x和y映射为NHClientRepository和Client,然后允许调用工作。由于编译器在编译时知道您正在执行的操作,因此能够为您提供帮助。如果您在不调用的情况下调用insertMethod
,您应该能够看到intellisense能够帮助您。
我认为那是你最后一块拼图?