【问题标题】:Expanding collections with EntitySetController in MVC Web Api在 MVC Web Api 中使用 EntitySetController 扩展集合
【发布时间】:2013-12-24 18:44:58
【问题描述】:

我将 OData 框架(版本 5.0.0)与 MVC Web Api(MVC 版本 4.0.305060)一起使用,并且无法让 $expand 查询为我的集合工作。

根据this wiki page,更具体地说,“支持的方案”下的案例#2,我应该能够扩展集合以将它们的数据包含在单个查询中,但我的 $expand 查询似乎被完全忽略了。没有错误,响应与我完全省略 $expand 完全相同。

这是(部分)我的数据结构:

DebtCalculation
    - string Name
    - string Description
    - Collection<Expense> Expenses
    - Collection<Participant> Participants

Participant
    - bool HasPaid
    - User User
    - DebtCalculation DebtCalculation

Expense
    - decimal Amount
    - User Payer
    - Collection<Debtor> Debtors
    - DebtCalculation DebtCalculation

总结;通过 myservice/api/Expenses?$expand=Payermyservice/api/Expenses(10)?$expand=Payer 使用 $expand 查询单个实体按预期工作,但查询 myservice/api/DebtCalculations?$expand=Participants 则不行。

这是我的控制器和 ODataConventionModelBuilder 设置的(部分):

[Authorize]
public class DebtCalculationsController : EntitySetController<DebtCalculation, long>
{
    private DebtCalculationManager _debtCalculationManager { get; set; }

    public DebtCalculationsController(DebtCalculationManager debtCalculationManager)
    {
        _debtCalculationManager = debtCalculationManager;
    }

    public override IQueryable<DebtCalculation> Get()
    {
        return _debtCalculationManager.AllDebtCalculationsForApi();
    }

    protected override DebtCalculation GetEntityByKey(long key)
    {
        return _debtCalculationManager.GetDebtCalculation(key);
    }

    public IQueryable<Participant> GetParticipants(long key)
    {
        return _debtCalculationManager.GetDebtCalculation(key).Participants.AsQueryable();
    }

    public IQueryable<Expense> GetExpenses(long key)
    {
        return _debtCalculationManager.GetDebtCalculation(key).Expenses.AsQueryable();
    }
}

[Authorize]
public class ExpensesController : EntitySetController<Expense, long>
{
    private DebtCalculationManager _debtCalculationManager { get; set; }

    public ExpensesController(DebtCalculationManager debtCalculationManager)
    {
        _debtCalculationManager = debtCalculationManager;
    }

    public override IQueryable<Expense> Get()
    {
        return _debtCalculationManager.AllDebtCalculationsForApi()
            .SelectMany(dc => dc.Expenses);
    }

    protected override Expense GetEntityByKey(long key)
    {
        return _debtCalculationManager.ExpenseForApi(key);
    }

    public User GetPayer(long key)
    {
        return _debtCalculationManager.ExpenseForApi(key).Payer;
    }

    public IQueryable<Debtor> GetDebtors(long key)
    {
        return _debtCalculationManager.ExpenseForApi(key).Debtors.AsQueryable();
    }

    public DebtCalculation GetDebtCalculation(long key)
    {
        return _debtCalculationManager.ExpenseForApi(key).DebtCalculation;
    }
}

[Authorize]
public class ParticipantsController : EntitySetController<Participant, long>
{
    private DebtCalculationManager _debtCalculationManager { get; set; }

    public ParticipantsController(DebtCalculationManager debtCalculationManager)
    {
        _debtCalculationManager = debtCalculationManager;
    }

    public override IQueryable<Participant> Get()
    {
        return _debtCalculationManager.AllDebtCalculationsForApi()
            .SelectMany(dc => dc.Participants);
    }

    protected override Participant GetEntityByKey(long key)
    {
        return _debtCalculationManager.ParticipantForApi(key);
    }

    public DebtCalculation GetDebtCalculation(long key)
    {
        return _debtCalculationManager.ParticipantForApi(key).DebtCalculation;
    }
}

public static class WebApiConfig
{
    public static HttpConfiguration Config { get; set; }

    public static void Register(HttpConfiguration config)
    {
        var modelBuilder = new ODataConventionModelBuilder();

        modelBuilder.EntitySet<DebtCalculation>("DebtCalculations")
            .EntityType.HasKey(dc => dc.Id);

        modelBuilder.EntitySet<Participant>("Participants")
            .EntityType.HasKey(p => p.Id)
            .Ignore(p => p.UniqueKey);

        modelBuilder.EntitySet<Expense>("Expenses")
            .EntityType.HasKey(e => e.Id);

        ...

        var model = modelBuilder.GetEdmModel();

        var routingConventions = ODataRoutingConventions.CreateDefault();
        routingConventions.Insert(0, new CreateNavigationPropertyRoutingConvention());

        config.Routes.MapODataRoute("ODataRoute", "api", model, new DefaultODataPathHandler(), routingConventions);

        config.EnableQuerySupport();
    }

    // routing convention to handle POST requests to navigation properties.
    public class CreateNavigationPropertyRoutingConvention : EntitySetRoutingConvention
    {
        public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
        {
            if (odataPath.PathTemplate == "~/entityset/key/navigation" && controllerContext.Request.Method == HttpMethod.Post)
            {
                IEdmNavigationProperty navigationProperty = (odataPath.Segments[2] as NavigationPathSegment).NavigationProperty;
                controllerContext.RouteData.Values["key"] = (odataPath.Segments[1] as KeyValuePathSegment).Value; // set the key for model binding.
                return "PostTo" + navigationProperty.Name;
            }

            return null;
        }
    }

DebtCalculation 的元数据如下所示:

    <EntityType Name="DebtCalculation">
            <Key>
                    <PropertyRef Name="Id"/>
            </Key>
            <Property Name="Id" Type="Edm.Int64" Nullable="false"/>
            <Property Name="Name" Type="Edm.String"/>
            <Property Name="Description" Type="Edm.String"/>
            <NavigationProperty Name="Participants" Relationship="Sujut.Core.Sujut_Core_DebtCalculation_Participants_Sujut_Core_Participant_ParticipantsPartner" ToRole="Participants" FromRole="ParticipantsPartner"/>
            <NavigationProperty Name="Expenses" Relationship="Sujut.Core.Sujut_Core_DebtCalculation_Expenses_Sujut_Core_Expense_ExpensesPartner" ToRole="Expenses" FromRole="ExpensesPartner"/>
    </EntityType>

发现任何问题?

编辑:

问题似乎在于将 ODataController 查询与 Linq-to-NHibernate 相结合。这是堆栈跟踪。升级到 NH 4.0.0.1000 alpha 版本并没有解决问题。 This bug report 似乎是同一个问题。

System.ArgumentException
at System.Linq.Expressions.Expression.Condition(Expression test, Expression ifTrue, Expression ifFalse)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitConditionalExpression(ConditionalExpression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.Visitors.NhExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitBinaryExpression(BinaryExpression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.Visitors.NhExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitConditionalExpression(ConditionalExpression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.Visitors.NhExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberBinding(MemberBinding memberBinding)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberBindingList(ReadOnlyCollection`1 expressions)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.Visitors.NhExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberBinding(MemberBinding memberBinding)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberBindingList(ReadOnlyCollection`1 expressions)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)
 at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.Visitors.NhExpressionTreeVisitor.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)
 at NHibernate.Linq.NestedSelects.NestedSelectRewriter.ReWrite(QueryModel queryModel, ISessionFactory sessionFactory)
 at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root)
 at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory)
 at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(String queryIdentifier, IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
 at NHibernate.Engine.Query.HQLExpressionQueryPlan.CreateTranslators(String expressionStr, IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
 at NHibernate.Engine.Query.HQLExpressionQueryPlan..ctor(String expressionStr, IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
 at NHibernate.Engine.Query.HQLExpressionQueryPlan..ctor(String expressionStr, IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
 at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
 at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
 at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
 at NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query, NhLinqExpression& nhQuery)
 at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
 at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
 at Remotion.Linq.QueryableBase`1.System.Collections.IEnumerable.GetEnumerator()
 at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)
 at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
 at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
 at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
 at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
 --- End of stack trace from previous location where exception was thrown ---
 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
 at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
 at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__14.MoveNext()

【问题讨论】:

  • DebtCollection 的元数据是否具有参与者的 NavigationProperty?
  • 将债务计算的元数据添加到问题中。

标签: asp.net-mvc-4 asp.net-web-api odata linq-to-nhibernate


【解决方案1】:

我遇到了同样的问题,我尝试了解决方法,但是对于大数据集,由于延迟加载,它似乎触发了很多选择语句。

所以我采取了不同的方法并手动加入了相关的表。

  // Using Chinook Database
  Album albumAlias = null;
  Track trackAlias = null;
  Artist artistAlias = null;
  return session.QueryOver<Album>(() => albumAlias)
    .Left.JoinAlias(() => albumAlias.Tracks, () => trackAlias)
    .Left.JoinAlias(() => albumAlias.Artist, () => artistAlias)
    .TransformUsing(Transformers.DistinctRootEntity)
    .List().AsQueryable();

专辑有许多曲目和一个与之关联的艺术家。 这样,它只会生成一个 select 语句,并且仍然可以扩展任一属性。

对于您的示例,它可能看起来像

DebtCalculation debtCalculationAlias = null;
Expense expenseAlias = null;
Participant participantAlias = null;
session.QueryOver<DebtCalculation>(() => debtCalculationAlias)
.Left.JoinAlias(() => debtCalculationAlias.Expenses, () => expenseAlias)
.Left.JoinAlias(() => debtCalculationAlias.Participants, () => participantAlias)
.TransformUsing(Transformers.DistinctRootEntity)
.List().AsQueryable();

我刚刚进入 OData,所以如果您认为这不是正确的方法,请随时纠正我。

【讨论】:

    【解决方案2】:

    问题在于 Linq-to-NHibernate,错误报告为 here

    通过在返回的 IQueryable&lt;DebtCalculation&gt; 集合上调用 .ToList().AsQueryable() 来解决此问题。这显然不是很有效,但可以让我继续开发和测试依赖 API 的应用程序,直到错误得到修复。

    【讨论】:

      猜你喜欢
      • 2014-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多