我正在尝试使用ASP.NET Core和EntityFramework Core在Linq中使用左连接。
两个表的简单情况:
我尝试查询的数据是Person.id,Person.firstname,Person.lastname和PersonDetails.DetailText。 有些人没有DetailText,所以想要的结果是NULL。
在SQL中它可以正常工作
SELECT p.id, p.Firstname, p.Lastname, d.DetailText FROM Person p
LEFT JOIN PersonDetails d on d.id = p.Id
ORDER BY p.id ASC
按预期结果:
# | id | firstname | lastname | detailtext
1 | 1 | First1 | Last1 | details1
2 | 2 | First2 | Last2 | details2
3 | 3 | First3 | Last3 | NULL
在我的Web API控制器中查询:
[HttpGet]
public IActionResult Get()
{
var result = from person in _dbContext.Person
join detail in _dbContext.PersonDetails on person.Id equals detail.PersonId
select new
{
id = person.Id,
firstname = person.Firstname,
lastname = person.Lastname,
detailText = detail.DetailText
};
return Ok(result);
}
招摇的结果是缺少第3人(没有详细文本的人)
[
{
"id": 1,
"firstname": "First1",
"lastname": "Last1",
"detailText": "details1"
},
{
"id": 2,
"firstname": "First2",
"lastname": "Last2",
"detailText": "details2"
}
]
我在Linq做错了什么?
感谢您提供答案和目前的链接。
我使用into
和.DefaultIfEmpty()
复制并粘贴了下面的代码,经过一些进一步的阅读后,我明白这应该有效。
不幸的是,它没有。
首先,代码开始抛出异常,但仍然返回前两个结果(缺少NULL)。从输出窗口复制粘贴:
System.NullReferenceException: Object reference not set to an instance of an object.
at lambda_method(Closure , TransparentIdentifier`2 )
at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
at Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter.WriteObject(TextWriter writer, Object value)
at Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter.d__9.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__32.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__29.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__23.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__18.MoveNext()
--- 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 Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- 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 Microsoft.ApplicationInsights.AspNetCore.ExceptionTrackingMiddleware.d__4.MoveNext()
--- 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 Microsoft.ApplicationInsights.AspNetCore.RequestTrackingMiddleware.d__4.MoveNext()
--- 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 Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.d__8.MoveNext()
--- 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 Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.d__3.MoveNext()
--- 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame`1.d__2.MoveNext()
Microsoft.AspNetCore.Server.Kestrel:Error: Connection id "0HKVGPV90QGE0": An unhandled exception was thrown by the application.
System.NullReferenceException: Object reference not set to an instance of an object.
at lambda_method(Closure , TransparentIdentifier`2 )
at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
at Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter.WriteObject(TextWriter writer, Object value)
at Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter.d__9.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__32.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__29.MoveNext()
--- 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 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__23.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__18.MoveNext()
--- 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 Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- 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 Microsoft.ApplicationInsights.AspNetCore.ExceptionTrackingMiddleware.d__4.MoveNext()
--- 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 Microsoft.ApplicationInsights.AspNetCore.RequestTrackingMiddleware.d__4.MoveNext()
--- 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 Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.d__8.MoveNext()
--- 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 Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.d__3.MoveNext()
--- 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame`1.d__2.MoveNext()
谷歌给了我一个:"LEFT OUTER JOIN PROBLEMS #4002"
以及"Left outer join @ Stackoverflow"
现在我不确定这是否仍然存在或应该已经修复的错误。我正在使用EntityFramework Core RC2。
正如Gert Arnold在评论中指出:使用导航属性
这意味着(工作)查询看起来像
var result = from person in _dbContext.Person
select new
{
id = person.Id,
firstname = person.Firstname,
lastname = person.Lastname,
detailText = person.PersonDetails.Select(d => d.DetailText).SingleOrDefault()
};
return Ok(result);
在我的PersonExampleDB中,我没有正确设置外键,因此属性PersonDetails
不在scaffolded模型类中。但是使用它是最简单的解决方案(并且工作甚至可以快速工作)而不是现在的连接(参见错误报告)。
当加入方式有效时,仍然对更新感到满意。
答案 0 :(得分:15)
如果您需要执行左连接,则必须使用into
和DefaultIfEmpty()
,如下所示。
var result = from person in _dbContext.Person
join detail in _dbContext.PersonDetails on person.Id equals detail.PersonId into Details
from m in Details.DefaultIfEmpty()
select new
{
id = person.Id,
firstname = person.Firstname,
lastname = person.Lastname,
detailText = m.DetailText
};
您可以了解有关它的更多信息:Left Outer Join in LINQ to Entities
答案 1 :(得分:2)
你没有做左连接,你正在使用的linq基本上是创建一个内连接。对于linq中的左连接,请使用关键字
[HttpGet]
public IActionResult Get()
{
var result = from person in _dbContext.Person
join detail in _dbContext.PersonDetails on person.Id equals detail.PersonId
into Details
from defaultVal in Details.DefaultIfEmpty()
select new
{
id = person.Id,
firstname = person.Firstname,
lastname = person.Lastname,
detailText = defaultVal.DetailText
};
return Ok(result);
}
答案 2 :(得分:0)
我同意这篇文章的作者-它仍然看起来像是个bug!如果连接表为空,则始终会收到“对象引用未设置为对象实例”。唯一的方法是将联接表检查为空:
IEnumerable<Models.Service> clubServices =
from s in services
from c in clubs.Where(club => club.ClubId == s.ClubId).DefaultIfEmpty()
from t in clubs.Where(tenant => tenant.TenantId == c.TenantId).DefaultIfEmpty()
select new Models.Service
{
ServiceId = s.ServiceId.ToString(),
ClubId = c == null ? (int?)null : c.ClubId,
ClubName = c == null ? null : c.Name,
HasTimeTable = s.HasTimeTable,
MultipleCount = s.MultipleCount,
Name = s.Name,
Tags = s.Tags.Split(';', StringSplitOptions.RemoveEmptyEntries),
TenantId = t == null ? (int?)null : t.TenantId,
TenantName = t == null ? null : t.Name
};
我无法检查“ detailText = person.PersonDetails.Select(d => d.DetailText).SingleOrDefault()
”,因为我的联接表位于不同的数据库中。