使用StructureMap / ASP.MVC / Visual Studio / LinqToSql的奇怪行为

时间:2009-03-16 11:40:48

标签: asp.net-mvc linq-to-sql dependency-injection structuremap

我最近一直在使用带有StructureMap的新MVC框架并且总体上已经取得了很好的结果,但是,我一直遇到一个我无法理解的非常奇怪的错误或者解决了如何解决。

这是我的架构:

  • DBContext - linqToSql数据上下文。
  • IRepository - 定义数据方法的合同。
  • IService - 定义服务方法的合同。
  • 控制器 - 本例中为两个。
因此,我有:

public class Repo : IRepository
{
    public Repo(DBContext db)
    {
       .....
    }
}

public class Service : IService
{
    public Service(IRepository repo)
    {
       .....
    }
}

public class ControllerOne : Controller
{
    public ControllerOne(IService service)
    {
       .....
    }
}

public class ControllerTwo : Controller
{
    public ControllerTwo(IService service)
    {
       .....
    }
}

StructureMap用于定义IRepository和IService的具体类型,DBContext由lamba表达式构成 - ()=>由DSL注册表配置的新DBContext()。

目前没有DBContext的缓存

解决问题:

我的索引页面加载并同时向ControllerOne和ControllerTwo发出两个Ajax请求,这些请求是通过MvcContrib的StructureMap控制器工厂构建的。

StructureMap正在注入IService的具体类型,而IService又是使用配置的IRepository实例和新的DBContext对象创建的。

ControllerOne正在从IService实例请求一个模型,然后将其作为JsonActionResult返回,该模型由Newtonsoft.Json呈现。

ControllerTwo正在从IService实例请求一个不同的模型,当MVC框架执行ActionResult时,它也被序列化为Json对象。

我在VS2008中通过Cassini运行网站。

我不时会看到的问题是LinqToSql内部生成的错误

  • 数据无法读取,已经有读者打开或
  • 无法将数据加载到数据表中,因为数据已经存在(我目前没有确切的例外情况,但两者都是LinqToSql内部的。)

如果ControllerOne中发生错误,那么ControllerTwo也会因类似错误而失败,就好像这两个请求正在运行共享对象一样。

它不会一直出错,但它足以让我担心我的架构,并且它在某种程度上配置错误。

结构映射是否有可能在后续请求中返回相同的ControllerOne和ControllerTwo实例,或者它是否以任何方式缓存DBContext?即使我不是要求它?

在Visual Studio / Cassini中工作时有没有人见过类似的东西?通过IIS有帮助吗?

我应该删除LinqToSql吗?

关闭Visual Studio并再次打开通常可以解决问题一段时间。

非常感谢,如果有人能够解决这个问题。

编辑:包含来自NLog日志文件的日志片段(线程ID是半冒号前的数字):

03/20/2009 01:40:32 12: controller=Timesheet,date=2001-05-06,Action=WeekEnding /beta/Timesheet/2001-05-06?_dc=1237513232397 
03/20/2009 01:40:32 10: controller=Timesheet,date=2001-05-06,Action=WeekEnding /beta/Timesheet/2001-05-06?_dc=1237513232449 
03/20/2009 01:40:32 10: There is already an open DataReader associated with this Command which must be closed first. System.InvalidOperationException[br]   at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
   at System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
   at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader()
   at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
   at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
   at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
   at System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
   at System.Linq.Queryable.Sum[TSource](IQueryable`1 source, Expression`1 selector)
   at HCD.Intranet.Core.Data.Linq.LinqEmployeeRepository.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding, Int32 nonProjectId)
   at HCD.Intranet.Core.Services.Impl.EmployeeService.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding)
   at HCD.Intranet.Core.Models.Timesheet.CalculateHolidaysRemaining()
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteTimesheet(JsonWriter writer, Timesheet timesheet)
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteJson(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.WriteMemberInfoProperty(JsonWriter writer, Object value, JsonMemberMapping memberMapping)
   at Newtonsoft.Json.JsonSerializer.SerializeObject(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at HCD.Intranet.Core.Web.Mvc.NewtonsoftJsonResult.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
03/20/2009 01:40:32 12: Invalid attempt to call Read when reader is closed. System.InvalidOperationException[br]   at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
   at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
   at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
   at System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
   at System.Linq.Queryable.Sum[TSource](IQueryable`1 source, Expression`1 selector)
   at HCD.Intranet.Core.Data.Linq.LinqEmployeeRepository.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding, Int32 nonProjectId)
   at HCD.Intranet.Core.Services.Impl.EmployeeService.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding)
   at HCD.Intranet.Core.Models.Timesheet.CalculateHolidaysRemaining()
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteTimesheet(JsonWriter writer, Timesheet timesheet)
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteJson(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.WriteMemberInfoProperty(JsonWriter writer, Object value, JsonMemberMapping memberMapping)
   at Newtonsoft.Json.JsonSerializer.SerializeObject(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at HCD.Intranet.Core.Web.Mvc.NewtonsoftJsonResult.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
03/20/2009 01:41:58 12: controller=Timesheet,month=6,year=2001,Action=Calendar /beta/Timesheet/Calendar/6/2001?_dc=1237513318470 
03/20/2009 01:41:59 10: controller=Timesheet,date=2001-06-03,Action=WeekEnding /beta/Timesheet/2001-06-03?_dc=1237513318509 
03/20/2009 01:41:59 12: The null value cannot be assigned to a member with type System.Int32 which is a non-nullable value type. System.InvalidOperationException[br]   at Read_TimesheetEntry(ObjectMaterializer`1 )
   at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at HCD.Intranet.Core.Data.Linq.LinqTimesheetRepository.GetEntries(Int32 timesheetHeaderId)
   at HCD.Intranet.Core.Services.Impl.TimesheetService.GetEntries(Int32 timesheetHeaderId)
   at HCD.Intranet.Core.Models.Timesheet.get_InnerEntries()
   at HCD.Intranet.Core.Models.TimeMap..ctor(Timesheet timesheet)
   at HCD.Intranet.Core.Models.Json.TimesheetCalendarJsonConverter.WriteTimesheet(JsonWriter writer, Timesheet[] timesheets)
   at HCD.Intranet.Core.Models.Json.TimesheetCalendarJsonConverter.WriteJson(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at HCD.Intranet.Core.Web.Mvc.NewtonsoftJsonResult.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
03/20/2009 01:41:59 12: controller=Timesheet,date=2001-06-03,Action=WeekEnding /beta/Timesheet/2001-06-03?_dc=1237513318545 
03/20/2009 01:41:59 12: There is already an open DataReader associated with this Command which must be closed first. System.InvalidOperationException[br]   at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
   at System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
   at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader()
   at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
   at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
   at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
   at System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
   at System.Linq.Queryable.Sum[TSource](IQueryable`1 source, Expression`1 selector)
   at HCD.Intranet.Core.Data.Linq.LinqEmployeeRepository.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding, Int32 nonProjectId)
   at HCD.Intranet.Core.Services.Impl.EmployeeService.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding)
   at HCD.Intranet.Core.Models.Timesheet.CalculateHolidaysRemaining()
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteTimesheet(JsonWriter writer, Timesheet timesheet)
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteJson(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.WriteMemberInfoProperty(JsonWriter writer, Object value, JsonMemberMapping memberMapping)
   at Newtonsoft.Json.JsonSerializer.SerializeObject(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at HCD.Intranet.Core.Web.Mvc.NewtonsoftJsonResult.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
03/20/2009 01:41:59 10: Invalid attempt to call Read when reader is closed. System.InvalidOperationException[br]   at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
   at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
   at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
   at System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
   at System.Linq.Queryable.Sum[TSource](IQueryable`1 source, Expression`1 selector)
   at HCD.Intranet.Core.Data.Linq.LinqEmployeeRepository.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding, Int32 nonProjectId)
   at HCD.Intranet.Core.Services.Impl.EmployeeService.CalculateHolidaysRemaining(Employee employee, DateTime weekEnding)
   at HCD.Intranet.Core.Models.Timesheet.CalculateHolidaysRemaining()
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteTimesheet(JsonWriter writer, Timesheet timesheet)
   at HCD.Intranet.Core.Models.Json.TimesheetJsonConverter.WriteJson(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.WriteMemberInfoProperty(JsonWriter writer, Object value, JsonMemberMapping memberMapping)
   at Newtonsoft.Json.JsonSerializer.SerializeObject(JsonWriter writer, Object value)
   at Newtonsoft.Json.JsonSerializer.SerializeValue(JsonWriter writer, Object value, JsonConverter memberConverter)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)
   at HCD.Intranet.Core.Web.Mvc.NewtonsoftJsonResult.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<InvokeActionResultWithFilters>b__e()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)

4 个答案:

答案 0 :(得分:6)

我正在和Jeremy Miller谈论这件事,而你希望用SM来管理数据库上下文的生命周期 - 让repo根据需要进行实例化。这会给你如何进行对象更新/持久化提出问题(如果你依赖于一个多个请求保持活动的上下文),但依赖于web应用程序是值得的。

由于这样的原因,我不得不从店面中删除db上下文管理的东西 - 我得到了内存泄漏。我不会说这是SM的错 - 但总的来说只是让回购开启一个新的背景。

答案 1 :(得分:2)

在ASP.NET MVC中使用DI容器时的一个常见错误是许多DI容器默认为Singleton模式。我不知道这是不是这样,但你应该仔细检查。 ASP.NET MVC要求在每个请求上创建控制器,因为它具有每请求状态和上下文。

答案 2 :(得分:1)

添加:

MultipleActiveResultSets=True

到连接字符串的末尾(假设MSSQL 2005 +)

为您的linq上下文执行此操作:打开属性选项卡 - &gt;展开连接 - &gt;点击“连接字符串”上的“...” - &gt; '高级' - &gt; 'MultipleActiveResultSets' - &gt;真。

我今天自己解决了这个问题,我的架构几乎完全相同(除了结构图而不是结构图)。包括通过JS加载的两个控制器!

答案 3 :(得分:1)

您是否完全确定对象层次结构中没有任何内容正在缓存/或具有生命周期配置?我有一个带有配置的prod应用程序,就像这样:

    ForRequestedType<SomeDataContext>().TheDefault.Is.ConstructedBy(
        () => new SomeDataContext(someConnString);

它没有使用asp.net MVC。关于范围问题,如果你还没有设置任何东西,结构图将默认为PerRequest(不是asp.net请求,而是结构映射请求,就像每个.GetInstance调用一样) - http://structuremap.sourceforge.net/Scoping.htm。如果你对没有配置影响它是肯定的,那么看看mvc contrib或其他东西是否可能重用实例。


关于发布的例外信息。该错误涉及操作结果,操作结果过滤器,json序列化,自定义方法以及由于意外null而导致一个linq2sql调用失败的组合。这是许多不同作品的组合,其中一些我不知道。我会更安全地运行计算内容(调用linq2sql),当序列化到其他不涉及json序列化/操作结果片段的地方时调用。这只是一个疯狂的猜测,因为我不知道这些作品是如何/什么时候被调用的,以及他们内部采取了什么类型的行动。