协变参数数组的通用类型参数 [英] Generic Type parameters for covariant params array

查看:108
本文介绍了协变参数数组的通用类型参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个接口:

  IReadOnlyList< TEntity>执行< TEntity>(ILambdaQuery< TEntity>查询,参数ILambdaQuery< TAssociatedEntity> []关联),其中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讲师查询= LambdaQuery< Instructor>(i => i.Courses.Any(c => c.id == id));

可变课程= this.repo.Execute< Course>(courseQuery,studentsQuery,instructionsQuery);

我的问题围绕以下参数:

  params ILambdaQuery< TAssociatedEntity> []关联

因此您可以从示例中可以看到,关联数组可以具有不同类型的TAssociatedEntity。那么我该如何告诉编译器呢?



上面的界面无法完成任务。编译器说找不到名称空间名称 TAssociatedEntity的类型...



这不可能吗?有任何想法吗?



编辑-请求更多信息:



LambdaQuery类存储一个lambda表达式子句和一个相关联的实体,以方便获取。其中的许多方法传递给存储库Execute方法,该方法执行以下操作:

  public IReadOnlyList< TEntity> Execute(ILinqQuery< TEntity>查询,参数ILinqQuery< TAssociatedEntity> []关联)
{
var queryOver = this.GetSession()。QueryOver< TEntity>()。Where(query.WhereClause);

query.FetchExpressions.ToList()。ForEach(表达式=> queryOver = queryOver.Fetch(expression).Eager);

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

foreach(关联中的ILinqQuery< TEntity>关联)
{
var queryOverAssociation = this.GetSession()。QueryOver< TEntity>()。Where(association.WhereClause);

association.FetchExpressions.ToList()。ForEach(表达式=> queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);

queryOverAssociation.Future< TEntity>();
}

返回this.ExecuteInTransaction(()=> future.ToList());
}

它以批处理方式运行查询和所有相关查询,并返回请求的TEntity



编辑-更多信息



nHibernate并不出色。但是用我的简单术语来说,使用Future()执行的任何操作都会延迟到调用ToList()之前。



所以如果我有:

 班级人
{
公用字串编号{组; }
公共列表< Arm>武器{get;组; }
公共列表< Leg>腿{组; }
}

类Arm
{
公共字符串ID {get;组; }
公共列表< Hand>手{得到;组; }
}

class Leg
{
public string Id {get;组; }
公开清单< Foot>脚{得到;组; }
}

因此,如果Execute方法是为Person和person的TEntity调用的拥有一组武器,每个武器都有一只手和一组腿,每个武器都有一只脚,那么我们可能会调用Execute,看起来像:

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

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

然后它将以下内容批量发送到数据库:

  SELECT * FROM person WHERE personId ='123'; 
选择*从武器上进行联合操作h.armId = a.armId WHERE a.personId = 123

然后将从Execute方法返回一个(无腿)Person对象,

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


解决方案

很抱歉,长时间的等待,这就是我能告诉你的。



序言



我难以回答的一个原因是,问题设置的某些部分似乎并没有真正融合在一起,因此事情可能无法按照您(或我)的想象进行。确实,您显示的 Execute 方法似乎实际上并没有通过关联进行任何操作。就我解释事物而言,至少没有什么可以提供任何可观察到的结果。



我从没使用过NHibernate,但我确实使用过Hibernate,期望您确实不需要编写查询来获取关联的对象-只要您正确设置了关联,ORM应该能够为您解决这个问题,并且在查询以下内容时会急于加载关联的实体一个主要实体,或者在访问它们时懒加载它们。



但是即使您需要手动查询关联的实体,我也希望您需要将它们连接起来以某种方式返回到您的主要实体,除非您希望让您的 Execute 方法返回单独的主要实体列表和每个关联的实体类型(但是您最好摆脱 associations 参数,只是q分别为每个列表)。而且我在您的 Execute 代码中看不到任何内容,该代码将关联查询的结果与主要实体连接起来(甚至检索了这些查询的结果)。



不幸的是,对于您有关类型签名的实际问题至关重要,您打算如何计划使用查询对象以及如何使用它们,这仍然不完全清楚



但是,您仍然可以从任何尝试中找出答案所需的内容,因此,我只选择一种解释,说明所有这些都应该如何解决。



让我们讲到重点



从您的问题, Execute 将使用一个查询来查找要返回的主要实体的列表,并使用其他查询来查找将作为返回值的一部分返回的关联实体主要实体对象。我会将您的 Execute 实现解释为未完成的尝试。我还将假设您可以通过 associations 查询获得的关联实体列表包含足够的信息,以找出哪个关联实体连接到了哪个主要实体,即使那看起来很远



因此,简而言之,关联查询将仅为用来对关联对象的列表进行多次查询,并且通过一些魔术过程(关键是 not 涉及查询对象),这些列表中的实体将连接到正确的主要实体。



您的问题是如何将几个关联查询传递给 Execute



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

对于您的关联参数,您正试图同时传递 LambdaQuery< ; Student> LambdaQuery< Instructor> 在同一数组中,因此该数组的项目类型必须是两者的超类型。 object 总是可以这样做,但不是很有用:

  IReadOnlyList< ; TEntity>执行< TEntity>(ILambdaQuery< TEntity>查询,params object []关联); // 怎么办? 

您可以通过这种方式传递查询,但是尝试使用它们不会带来很多乐趣。相当重要的是,这不起作用:

  IReadOnlyList< TEntity>执行< TEntity>(ILambdaQuery< TEntity>查询,参数LambdaQuery< object> []关联); 

要使其正常工作,请使用 LambdaQuery< T> 必须是协变的,但这与您可以直接访问查询对象中的where子句(如果有的话必须是 contra variant)的事实冲突。不,解决方案不在这里。



如果您可以控制 LambdaQuery< T> 类层次结构,一种解决方案是使 LambdaQuery< T> 派生的另一个类 LambdaQuery (无类型参数!)从。然后,您可以传入 LambdaQuery 的列表:

  IReadOnlyList< TEntity> ;执行< TEntity>(ILambdaQuery< TEntity>查询,参数LambdaQuery []关联); //是的! 

LambdaQuery 是所有用户的常见超类型 LambdaQuery< T> ,因此现在可以编译了!但是,您如何使用它呢?因为我不知道如何将实体连接在一起,所以事情变得有些繁琐,但是请想象一下这样的事情:

 公共抽象类LambdaQuery 
{
public abstract void DoAssociationStuff< TMainEntity>(TMainEntity mainEntity,Session session);
}

公共抽象类LambdaQuery< TEntity> :LambdaQuery
{
public void DoAssociationStuff(Session session)
{
var queryOverAssociation = session.QueryOver< TEntity>()。Where(this.WhereClause);
this.FetchExpressions.ToList()。ForEach(表达式=> queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
queryOverAssociation.Future< TEntity>();
}
}

public IReadOnlyList< TEntity> Execute(LambdaQuery< TEntity>查询,参数LambdaQuery []关联)
{
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.DoAssociationStuff(this.GetSession());
}

返回this.ExecuteInTransaction(()=> future.ToList());
}

这里的想法是列出一些常见类型的东西不公开通用类型参数,但是可以将执行传递给知道通用类型参数的子类方法。



奖金:奖励与访客模式无关



您提到您不希望特定于 Execute 的代码成为 LamdaQuery 类层次结构的一部分。这很有道理,因为它实际上不必关心 LambdaQuery 的使用方式,并且您不想更改或添加它以备下次使用。



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

 公共接口LambdaQueryVisitor 
{
void Visit< TEntity>(已访问LambdaQuery< TEntity>);
}

公共抽象类LambdaQuery
{
公共抽象void Accept(LambdaQueryVisitor visitor);
}

公共类LambdaQuery< TEntity> :LambdaQuery
{
public over void void Accept(LambdaQueryVisitor visitor)
{
visitor.Visit(this);
}
}

这是您需要添加到 LambdaQuery< T> 及其超类 LambdaQuery ,它与要执行的操作无关。但是它使您可以通过实现访问者,在任何情况下使用LambdaQueries集合:

  public AssociationQueryVisitor:LambdaQueryVisitor 
{
私有只读会话m_session;

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

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

public IReadOnlyList< TEntity> Execute(LambdaQuery< TEntity>查询,参数LambdaQuery []关联)
{
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访问者=新的AssociationQueryVisitor(this.GetSession());

foreach(关联的LambdaQuery关联)
{
association.Accept(visitor);
}

返回this.ExecuteInTransaction(()=> future.ToList());
}

奖金奖金:动态



通过使用动态类型,您应该能够以更少的代码获得相同的结果,并且完全不必对 LambdaQuery< T> 进行任何更改。动态键入是一个非常强大的工具,但是在如何使用和何时使用上存在意见分歧。这是它的外观(我希望。这都是从内存中而不是经过检查的;))

  public void DoAssociationStuff< TEntity> ;(会话会话,LambdaQuery< TEntity>查询)
{
var queryOverAssociation = session.QueryOver< TEntity>()。Where(query.WhereClause);
query.FetchExpressions.ToList()。ForEach(表达式=> queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
queryOverAssociation.Future< TEntity>();
}

public IReadOnlyList< TEntity> Execute(LambdaQuery< TEntity>查询,参数dynamic []关联)
{
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(关联中的动态关联)
{
DoAssociationStuff(this.GetSession(),association);
}

返回this.ExecuteInTransaction(()=> future.ToList());
}

这里的缺点是您可以通过任何内容现在在该关联数组中,它只会在运行时失败。


I have an interface:

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

The interface is used to execute a query in a repository. Where TEntity is the type that you're expecting back and what you are querying and TAssociatedEntity are other queries that you'd like to run and that will be combined with the TEntity results that come back. Eg.

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

My question revolves around this parameter:

params ILambdaQuery<TAssociatedEntity>[] associations

So you can see from the example, the associations array could have different types of TAssociatedEntity. So how do I tell the compiler that?

The interface above doesn't do the job. The compiler says that The type of namespace name 'TAssociatedEntity' could not be found...

Is this inpossible? Any ideas out there?

EDIT - a request for more information:

The LambdaQuery class stores a where lambda expression clause and a associated entities to eager fetch. A multitude of these are passed to the repository Execute method which does:

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

It runs the query and any associated queries as a batch and returns a requested TEntity with any bits filled in.

EDIT - more information

I'm not brilliant with nHibernate. But in my simple terms, anything that is executed with Future() is delayed until the ToList() is called.

So if I had:

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

So if the Execute method was called for a TEntity of Person and a person had a collection of arms, each of which has a hand and a collection of legs, each of which has a foot, then we might have a called to Execute that looks like:

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

Then it would send the following to the database in batch:

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

And a (legless) Person object would be returned from the Execute method with:

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

解决方案

Sorry for the long wait, here is what I can tell you.

Preamble

One reason why I have trouble answering is that parts of the question setup don't really seem to fit together, so things might not really work the way you (or I) imagine. And indeed the Execute method you show does not seem to actually do anything with the associations that are passed in, at least nothing that would provide any observable result, as far as I interpret things.

I never used NHibernate, but I do use Hibernate, and I expect that you don't really need to write a query to get associated objects - as long as you set up the associations correctly, the ORM should be able to take care of that for you and either eagerly load the associated entities when you query for a main entity, or lazily load them when you access them.

But even if you need to query for the associated entities manually, I would expect that you need to connect them to your main entities somehow, unless you want to have your Execute method return separate lists of main entities and each associated entity type (but then you might as well get rid of the associations parameter and just query for each list individually). And I don't see anything in your Execute code which connects up the results of the association queries with the main entities (or even retreives the results of those queries).

Unfortunately, it is critical for your actual question about type signatures how exactly you plan to use the query objects, and what you can do with them, and that is still not entirely clear to me.

However, you might still figure out what you need from any attempt at an answer, so I'll just choose one interpretation of how this is all supposed to work and work from that.

Let's get to the point

From your question, Execute will take one query to find the list of "main" entities to return, and additional queries which are used to find associated entities that will be returned as part of the main entity objects. I'll interpret your Execute implementation as an unfinished attempt to make this work. I will also assume that the lists of associated entities you can get through the associations queries contain enough information to figure out which associated entity connects to which main entity, even if that seems far from trivial to me.

So in short, the associations queries would only be used to make several queries for lists of the associated objects, and by some magic process (critically not involving the query objects) the entities in those lists would be connected to the correct main entities.

Your question is how you can pass several associations queries into Execute, which may be queries for different types of entities and thus have different generic type parameters.

First off, lists and arrays can only hold objects of one fixed type. You can put a String and a Timer into the same array, but only if the item type for the array is a common superclass of both - in this case, only an array of object would work.

For your associations parameter, you are trying to pass both LambdaQuery<Student> and LambdaQuery<Instructor> in the same array, so the array's item type would need to be a supertype of both. object always works for this, but is not very useful:

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

You can pass in your queries this way, but you will not have much fun trying to use them. What does NOT work, quite importantly, is this:

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

For that to work, LambdaQuery<T> would have to be covariant, but that would clash with the fact that you can directly access the where clause in the query object which would have to be contravariant if anything. No, the solution does not lie in generic variance here.

If you have control over the LambdaQuery<T> class hierarchy, one solution could be to make another class LambdaQuery (without type parameter!) which LambdaQuery<T> derives from. Then you could pass in a list of LambdaQuery:

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

LambdaQuery would be a common supertype for all LambdaQuery<T>, so this will now compile! However, how do you work with it? This is where things get a little hairy since I don't know how you would connect the entities together, but imagine something like this:

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

The idea here is to have a list of things of some common type which does not expose the generic type parameter, but which can pass execution to a subclass method which knows about the generic type parameter.

I hope this helps you, or at least gives some inspiration for finding a solution.

Bonus: Seperate concerns with the visitor pattern

You mentioned you don't want the Execute-specific code to be part of the LamdaQuery class hierarchy. This makes a lot of sense, because it really should not concern the LambdaQuery how it is used, and you don't want to change it or add to it for the next use case that comes along.

Fortunately there is a solution, but as always with these things it requires a bit of indirection. The idea is to implement the visitor pattern for 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);
    }
}

This is all the code you need to add to LambdaQuery<T> and its superclass LambdaQuery, and it is not concerned with the specifics of what you want to do. But it enables you to work with collections of LambdaQueries in any context, by implementing a visitor:

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

Bonus Bonus: dynamic

You should be able to get the same result with less code and without any change to LambdaQuery<T> at all by using dynamic typing. Dynamic typing is a very powerful tool, but opinions differ wildly on how and when it is appropriate to use. Here is how it would look (I hope. This is all from memory and not checked ;))

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

The downside here is that you could pass anything in that associations array now, and it would only fail at runtime.

这篇关于协变参数数组的通用类型参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆