协变参数数组的通用类型参数

Generic Type parameters for covariant params array

我有一个接口:

IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params ILambdaQuery<TAssociatedEntity>[] associations) where TAssociatedEntity : class, IAggregateRoot;

该接口用于在存储库中执行查询。其中 TEntity 是您期望返回的类型以及您正在查询的内容,而 TAssociatedEntity 是您想要 运行 的其他查询,并且将与返回的 TEntity 结果相结合。例如

var courseQuery = LambdaQuery<Course>(c => c.Id == id);

var studentsQuery = LambdaQuery<Student>(s => s.Courses.Any(c => c.id == id));

var instructorsQuery = LambdaQuery<Instructor>(i => i.Courses.Any(c => c.id == id));

var courses = this.repo.Execute<Course>(courseQuery, studentsQuery, instructorsQuery);

我的问题围绕着这个参数:

params ILambdaQuery<TAssociatedEntity>[] associations

所以你可以从例子中看出,associations数组可以有不同类型的TAssociatedEntity。那么我该如何告诉编译器呢?

上面的界面不能完成这项工作。编译器说找不到名称空间名称的类型 'TAssociatedEntity'...

这不可能吗?有什么想法吗?

编辑 - 请求更多信息:

LambdaQuery class 存储一个 where lambda 表达式子句和一个关联的实体以急切获取。许多这些被传递到存储库 Execute 方法,该方法执行:

    public IReadOnlyList<TEntity> Execute(ILinqQuery<TEntity> query, params ILinqQuery<TAssociatedEntity>[] associations)
    {
        var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);

        query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);

        var future = queryOver.Future<TEntity>();

        foreach (ILinqQuery<TEntity> association in associations)
        {
            var queryOverAssociation = this.GetSession().QueryOver<TEntity>().Where(association.WhereClause);

            association.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);

            queryOverAssociation.Future<TEntity>();
        }

        return this.ExecuteInTransaction(() => future.ToList());
    }

它 运行 将查询和任何关联的查询作为一个批处理,returns 请求的 TEntity 填充了任何位。

编辑 - 更多信息

我对 nHibernate 不是很了解。但简单来说,使用 Future() 执行的任何操作都会延迟到调用 ToList() 时。

所以如果我有:

class Person
{
   public string Id { get; set; }
   public List<Arm> Arms { get; set; }
   public List<Leg> Legs { get; set; }
}

class Arm
{
   public string Id { get; set; }
   public List<Hand> Hands { get; set; }
}

class Leg
{
   public string Id { get; set; }
   public List<Foot> Feet { get; set; }
}

因此,如果为一个 TEntity of Person 调用了 Execute 方法,并且一个人有一组手臂,每只手臂都有一只手,还有一组腿,每只腿都有一只脚,那么我们可能有一个调用 Execute 看起来像:

LambdaQuery<Person> personQuery = new LambdaQuery<Person>(t => t.Id == "123");
LambdaQuery<Arm> armQuery = new LambdaQuery<Arm>(t => t.Person.Id == "123", t => t.Hands);

Person person = personRepository.Execute(personQuery, armQuery);

然后它会向数据库批量发送以下内容:

SELECT * FROM person WHERE personId = '123';
SELECT * FROM arms a JOIN hands h ON h.armId = a.armId WHERE a.personId = "123"

一个(无腿的)Person 对象将从 Execute 方法返回:

Person
------
{ 
  Id : "123", 
  Arms : [ { Id : "ARM1", Hands : [ { Id : "HAND1" }, { Id : "HAND2" } ] } ], 
  Legs: null 
}

您还没有将 TAssociatedEntity 类型声明为泛型类型,这可以通过两种方式完成。

要么修改方法(这可能是你想要做的)

IReadOnlyList<TEntity> Execute<TEntity,TAssociatedEntity>...

或接口,如果 TAssociatedEntity 必须是协变的。

public interface IDontKnow<in TAssociatedEntity>

抱歉让您久等了,这就是我可以告诉您的。

序言

我无法回答的一个原因是问题设置的某些部分似乎并不真正适合在一起,所以事情可能并不像您(或我)想象的那样有效。实际上,您显示的 Execute 方法似乎实际上并没有 对传入的 associations 任何事情,至少没有任何东西可以提供任何可观察到的结果,就我的解释而言。

我从未使用过 NHibernate,但我确实使用过 Hibernate,并且我希望您真的不需要编写查询来获取关联对象 - 只要您正确设置关联,ORM 应该能够为您解决这个问题,并在您查询主要实体时急切加载关联实体,或者在您访问它们时延迟加载它们。

但即使您需要手动查询关联实体,我也希望您需要以某种方式将它们连接到您的主要实体,除非您想要 Execute 方法 return单独的主要实体列表和每个关联的实体类型(但是你也可以去掉 associations 参数并单独查询每个列表)。而且我在您的 Execute 代码中没有看到任何将关联查询的结果与主要实体联系起来的内容(甚至检索这些查询的结果)。

不幸的是,对于你关于类型签名的实际问题,你计划如何使用查询对象,以及你可以用它们做什么,这对我来说仍然不是很清楚。

但是,您可能仍然会从任何尝试中找出您需要什么答案,所以我将只选择一种解释来解释这一切应该如何工作,并以此为基础。

进入正题

根据您的问题,Execute 将进行一次查询以查找 "main" 到 return 个实体的列表,以及用于查找关联实体的其他查询 return =120=]ed 作为主要实体对象的一部分。我会将您的 Execute 实施解释为完成这项工作的未完成尝试。我还将假设您可以通过 associations 查询获得的关联实体列表包含足够的信息来确定哪个关联实体连接到哪个主要实体,即使这对我来说似乎很重要。

所以简而言之,associations 查询将 用于对关联对象列表进行多个查询,并通过一些神奇的过程(关键 涉及查询对象)这些列表中的实体将连接到正确的主要实体。

您的问题是如何将多个 associations 查询传递给 Execute,这些查询可能是针对不同类型实体的查询,因此具有不同的通用类型参数。

首先,列表和数组只能保存一种固定类型的对象。您可以将一个 String 和一个 Timer 放入同一个数组中,但前提是该数组的项类型是两者的公共 superclass - 在这种情况下,只有 object 的数组才有效。

对于您的 associations 参数,您试图在同一数组中同时传递 LambdaQuery<Student>LambdaQuery<Instructor>,因此数组的项目类型需要是两者的超类型。 object 总是适用于此,但不是很有用:

IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params object[] associations); // now what?

您可以通过这种方式传递查询,但尝试使用它们不会有太多乐趣。什么不起作用,非常重要的是:

IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params LambdaQuery<object>[] associations);

要使其工作,LambdaQuery<T> 必须是协变的,但这会与您可以直接访问查询对象中的 where 子句的事实发生冲突,该子句必须是 contra变体如果有的话。不,解决方案不在于这里的通用方差。

如果您可以控制 LambdaQuery<T> class 层次结构,一个解决方案可能是创建另一个 class LambdaQuery(没有类型参数!),其中 LambdaQuery<T> 源自。然后你可以传入 LambdaQuery:

的列表
IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params LambdaQuery[] associations); // Yes!

LambdaQuery 将是所有 LambdaQuery<T> 的通用超类型,所以现在可以编译了!但是,您如何使用它?这是事情变得有点毛茸茸的地方,因为我不知道如何将实体连接在一起,但想象一下这样的事情:

public abstract class LambdaQuery
{
    public abstract void DoAssociationStuff<TMainEntity>(TMainEntity mainEntity, Session session);
}

public abstract class LambdaQuery<TEntity> : LambdaQuery
{
    public void DoAssociationStuff(Session session)
    {
        var queryOverAssociation = session.QueryOver<TEntity>().Where(this.WhereClause);
        this.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
        queryOverAssociation.Future<TEntity>();
    }
}

public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params LambdaQuery[] associations)
{
    var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
    query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
    var future = queryOver.Future<TEntity>();

    foreach (LambdaQuery association in associations)
    {
        association.DoAssociationStuff(this.GetSession());
    }

    return this.ExecuteInTransaction(() => future.ToList());
}

这里的想法是拥有一些不公开泛型类型参数但可以将执行传递给知道泛型类型参数的 subclass 方法的一些常见类型的事物列表。

希望对您有所帮助,或者至少能给您一些寻找解决方案的启发。

奖励:与访问者模式分开关注

您提到您不希望 Execute 特定代码成为 LamdaQuery class 层次结构的一部分。这很有意义,因为它真的不应该关心 LambdaQuery 它是如何使用的,你不想为下一个出现的用例更改它或添加它。

幸运的是有一个解决方案,但与往常一样,它需要一些间接的方法。这个想法是为 LambdaQuery 实现访问者模式:

public interface LambdaQueryVisitor
{
    void Visit<TEntity>(LambdaQuery<TEntity> visited);
}

public abstract class LambdaQuery
{
    public abstract void Accept(LambdaQueryVisitor visitor);
}

public class LambdaQuery<TEntity> : LambdaQuery
{
    public override void Accept(LambdaQueryVisitor visitor)
    {
        visitor.Visit(this);
    }
}

这是您需要添加到 LambdaQuery<T> 及其超级 class LambdaQuery 的所有代码,它与您要执行的操作的细节无关。但它使您能够通过实施访问者在任何上下文中使用 LambdaQueries 集合:

public AssociationQueryVisitor : LambdaQueryVisitor
{
    private readonly Session m_session;

    public AssociationQueryVisitor(Session session)
    {
        m_session = session;
    }

    public void Visit<TEntity>(LambdaQuery<TEntity> query)
    {
        var queryOverAssociation = m_session.QueryOver<TEntity>().Where(query.WhereClause);
        query.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
        queryOverAssociation.Future<TEntity>();
    }
}

public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params LambdaQuery[] associations)
{
    var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
    query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
    var future = queryOver.Future<TEntity>();

    AssociationQueryVisitor visitor = new AssociationQueryVisitor(this.GetSession());

    foreach (LambdaQuery association in associations)
    {
        association.Accept(visitor);
    }

    return this.ExecuteInTransaction(() => future.ToList());
}

奖金奖金:动态

通过使用动态类型,您应该能够用更少的代码获得相同的结果,并且根本不需要对 LambdaQuery<T> 进行任何更改。动态类型是一个非常强大的工具,但是关于如何以及何时使用它的合适意见却大相径庭。这是它的样子(我希望。这都是记忆中的,没有经过检查;))

public void DoAssociationStuff<TEntity>(Session session, LambdaQuery<TEntity> query)
{
    var queryOverAssociation = session.QueryOver<TEntity>().Where(query.WhereClause);
    query.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
    queryOverAssociation.Future<TEntity>();
}

public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params dynamic[] associations)
{
    var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
    query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
    var future = queryOver.Future<TEntity>();

    foreach (dynamic association in associations)
    {
        DoAssociationStuff(this.GetSession(), association);
    }

    return this.ExecuteInTransaction(() => future.ToList());
}

这里的缺点是您现在可以在该关联数组中传递 任何东西,它只会在运行时失败。