我想在实体框架中联接多个表,对此进行查询。
想要将查询的输出分配给没有任何表的复杂对象。
当我尝试这样做时,我遇到了错误。
EntityType'ConstraintFilter'没有定义键。定义此EntityType的键。 ConstraintFilters:EntityType:EntitySet'ConstraintFilters'基于未定义键的'ConstraintFilter'类型。
如何克服这个问题?
代码
[HttpGet]
[EnableQuery]
public IQueryable<DataPoint> GetDataPoints([FromUri] DateTime effectiveDate, [FromUri] string state, [FromUri] string dataPointName = "", [FromUri] ICollection<string> category = null, [FromUri] bool includeConstraints = false, [FromUri] ConstraintFilter constraintFilter = null)
{
return _db.DataPoints.Where(ent =>
ent.EffectiveDate <= effectiveDate && (ent.ExpiryDate == null || ent.ExpiryDate > effectiveDate)
&& ent.DataPointStates.Any(pr => pr.State == state && (pr.EffectiveDate <= effectiveDate && (pr.ExpiryDate == null || pr.ExpiryDate > effectiveDate))))
.Include(ent => ent.DataPointEnumerations)
.Include(ent => ent.DataPointExpressions.Select(dpe => dpe.Expression))
.Include(ent => ent.DataPointValidations.Select(dpv => dpv.Validation))
.Include(ent => ent.DataPointStates)
.Include(ent=>ent.ConstraintFilters)
.ToList().Select(ent => new DataPoint
{
Description = ent.Description,
EffectiveDate = ent.EffectiveDate,
ExpiryDate = ent.ExpiryDate,
Id = ent.Id,
Name = ent.Name,
IsVirtualField = ent.IsVirtualField,
DataPointStates = ent.DataPointStates.Where(ent2 =>
ent2.State == state && ent2.EffectiveDate <= effectiveDate &&
(ent2.ExpiryDate == null || ent2.ExpiryDate > effectiveDate)).ToArray(),
DataPointExpressions = ent.DataPointExpressions.Where(ent2 =>
ent2.EffectiveDate <= effectiveDate &&
(ent2.ExpiryDate == null || ent2.ExpiryDate > effectiveDate)).
Select(de => new DataPointExpression
{
Id = de.Id,
ExpressionId = de.ExpressionId,
DataPointId = de.DataPointId,
EffectiveDate = de.EffectiveDate,
ExpiryDate = de.ExpiryDate,
Expression = de.Expression
}).ToArray(),
DataPointEnumerations = ent.DataPointEnumerations.Where(ent2 =>
ent2.EffectiveDate <= effectiveDate &&
(ent2.ExpiryDate == null || ent2.ExpiryDate > effectiveDate)).ToArray(),
DataPointValidations = ent.DataPointValidations.Where(ent2 =>
ent2.EffectiveDate <= effectiveDate &&
(ent2.ExpiryDate == null || ent2.ExpiryDate > effectiveDate)).ToArray(),
ConstraintFilters = getConditions(),
}).AsQueryable();
}
答案 0 :(得分:1)
A,您忘记给我们看DataPoint
类,因此,我只能提供一些帮助您解决问题的建议。
从您的代码中,我看到每个DataPoint
都有几个一对多(或多对多)关系。
因为您对属性ConstraintFilters
使用了复数名词,所以似乎每个DataPoint
都有零个或多个ConstraintFilters
。这些ConstraintFilters
存储在单独的表中:
class DataPoint
{
public int Id {get; set;} // primary key
...
public virtual ICollection<ConstraintFilter> ConstraintFilters {get; set;}
}
在实体框架中,非虚拟属性表示数据库表中的列;虚拟属性表示表之间的关系(一对多,多对多等)
但是,从您的标题和错误来看,ConstraintFilters
似乎是一个(复杂)对象,即DataPoints
表中的列。
class DataPoint
{
public int Id {get; set;} // primary key
...
public ConstraintFilter ConstraintFilters {get; set;}
}
如果是这种情况:原因出在您的.Include(ent=>ent.ConstraintFilters)
中。不要使用它。只需访问您计划在查询中使用的属性,就会获取值。
建议:坚持使用entity framework code-first conventions,这可以提高代码的可读性。在您的情况下:如果您的DataPoint
有一个(复数类型)ConstraintFilter
,请不要使用复数ConstraintFilters
。
从您的DataPoints
集合中,您似乎想选择某些DataPoints
并查询其某些(或全部)属性。
在您的查询中,我看到了几个问题。
数据库查询的最慢部分之一是从数据库管理系统中的选定数据到本地进程的传输。因此,限制要传输的数据是明智的。
每个DataPoint
都有一个主键,可能在属性Id
中。每个DataPoint
都有零个或多个DataPointEnumerations
。每个DataPointEnumeration
恰好属于一个DataPoint
(一对多)。为此,DataPointEnumeration
具有一个外键DataPointId
,该外键的值与其所属的DataPoint.Id
的值相同。
如果用一个DataPoint
来查询它的1000 DataPointEnumerations
,您将知道此DataPointId
的每个DataPointEnumeration
中的每个DataPoint
具有相同的值,即DataPoint.Id
的值:一次又一次地转移相同的值真是浪费。
查询数据时,请勿使用
Include
。请改为使用Select
,仅使用Select
您实际打算使用的属性。仅在您计划更改和保存提取的包含数据时使用Include
。
只要保留数据IQueryable
,就不会执行查询。串联LINQ语句只会更改IQueryable.Expression
。
ToList
将执行查询并将数据传输到本地进程。之后,您再次将其设置为IQueryable
。
想象一下下面的代码会发生什么:
IQueryable<DataPoint> dataPointQuery = YourProcedure() // the procedure that return your query
DataPoint firsDataPoint = dataPointQuery.FirstOrDefault();
首先,您的ToList
会将与您的DataPoints
匹配的所有Where
提取到本地内存,然后您只提取第一个,并丢弃所有其他提取的DataPoints
:这很浪费的处理能力。
尽可能长时间地保持
IQueryable
和IQueryable
,从而允许用户连接LINQ语句而无需实际执行IQueryable
。
将数据传输到本地内存后执行linq语句的唯一原因是,您需要像在ConstraintFilters = getConditions()
中那样调用本地函数,该语句无法转换为SQL。这可能是您在查询中添加ToList()
的原因。
如果您开始枚举AsEnumerable
,则可以隐式使用ToList()
,FirstOrDefault()
,Any(),foreach
,或者隐式使用GetEnumerator
和{{ 1}},则MoveNext
将仅获取查询的页面,而不是完整数据。
AsEnumerable
IQueryable<...> sourceData = ...
var fetchdItem = sourceData
.AsEnumerable()
.Where(item => this.Localfunction(item)) // for this we need AsEnumerable
.FirstOrDefault();
将在内部FirstOrDefault
和GetEnumerator
中进行。 AsEnumerable将获取源数据的一个“页面”,这不是完整的集合。因此,如果仅使用MoveNext
,则将获取多个源项目,而不是全部,这会更有效率。仅当您枚举的项目超过一页的数量时,才会从数据库中查询下一页。
整理建议
FirstOrDefault()
,而是Include
,只能提取使用的数据Select
,而是ToList
,则每页获取数据而不是所有数据Enumerable
:数据位于本地内存中,IEnumerable
的作用还不止IEnumerable
。
IQueryable
在接下来的语句中,您将使用IEnumerable<DataPoint> FetchDataPoints(...)
{
return myDbContext.DataPoints
// select a subset of dataPoints:
// improve readability, use proper identifiers: not "ent", but "dataPoint"
.Where(dataPoint => ...)
// select only the properties you plan to use in this use-case scenario
.Select(dataPoint => new
{
Id = dataPoint.Id,
Description = dataPoint.Description,
EffectiveDate = dataPoint.EffectiveDate,
...
DataPointStates = dataPoint.DataPointStates
// only Select the dataPointStates I plan to use
.Where(dataPointState => ...))
// from every dataPointSate select only the properties I plan to use
.Select(dataPointState => new
{
...
// not needed: dataPointState.DataPointId, you know the value
})
.ToList(),
// query only the DataPointExpressions you plan to use,
DataPointExpressions = dataPoint.DataPointExpressions
.Where(dataPointExpression => ...)
.Select(dataPointExpression => new
{
// again select only the properties you plan to use
})
.ToList(),
})
之类的本地函数,因此必须将所选数据每页传输到本地内存中。
继续LINQ语句:
getConditions()
仅当您需要在类型化的对象中返回获取的数据时,才需要以下内容。如果仅在此代码块中使用数据,则无需将其转换为新的DataPoint:
.AsEnumerable()