Entity Framework - Сложный объект - PullRequest
0 голосов
/ 26 сентября 2018

Я хочу объединить несколько таблиц в Entity Framework, запрос против этого.

Хотите назначить вывод моего запроса сложному объекту, у которого нет таблицы.

При попытке сделать я получаю ошибку ниже.

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

1 Ответ

0 голосов
/ 27 сентября 2018

Увы, вы забыли показать нам класс 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).Не используйте это.Просто получите доступ к свойствам, которые вы планируете использовать в своем запросе, и значения будут извлечены.

Совет: придерживайтесь соглашений о коде в рамках структуры сущностей , это повышает удобочитаемость вашего кода.В вашем случае: если у вашего DataPoint есть один (сложный тип) ConstraintFilter, не используйте множественное число ConstraintFilters.

Проблемы в вашем запросе

Из вашей коллекции DataPoints вы, кажется, хотите выбрать определенное DataPoints и запросить некоторые (или все) его свойства.

В вашем запросе я вижу несколько проблем.

Вы получаете больше данных, чем на самом делеиспользуйте

Одна из более медленных частей запроса к базе данных - это передача выбранных данных из вашей системы управления базами данных в локальный процесс.Следовательно, имеет смысл ограничить объем передаваемых данных.

Каждый DataPoint имеет первичный ключ, вероятно, в свойстве Id.Каждый DataPoint имеет ноль или более DataPointEnumerations.Каждый DataPointEnumeration принадлежит ровно одному DataPoint (один ко многим).Для этого DataPointEnumeration имеет внешний ключ DataPointId, который имеет то же значение, что и DataPoint.Id, к которому он принадлежит.

Если вы запросите DataPoint с его 1000 DataPointEnumerations you 'Вы узнаете, что каждый DataPointId каждого DataPointEnumeration этого DataPoint имеет одно и то же значение, а именно значение DataPoint.Id: что за передача этого же значения снова и снова.

При запросе данных не используйте Include.Вместо этого используйте Select и Select только те свойства, которые вы действительно планируете использовать.Используйте Include только в том случае, если вы планируете изменить и сохранить извлеченные включенные данные.

После ToList () вы продолжите объединение операторов LINQ

До тех пор, пока вы сохраняете свои данные 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.

IQueryable и IEnumerable

Единственная причина для выполнения операторов linq после передачи данных в локальную память -потому что вам нужно вызывать локальные функции, как вы делаете в ConstraintFilters = getConditions() Этот оператор не может быть переведен в SQL.Это, вероятно, причина, по которой вы добавили ToList() в свой запрос.

Если вы начнете перечислять AsEnumerable, либо неявно используя ToList(), FirstOrDefault(), Any (), foreach, либонеявно используя GetEnumerator и MoveNext, AsEnumerable будет извлекать только страницу запроса, а не полные данные.

IQueryable<...> sourceData = ...
var fetchdItem = sourceData
    .AsEnumerable()
    .Where(item => this.Localfunction(item))        // for this we need AsEnumerable
    .FirstOrDefault();

FirstOrDefault будет внутренне GetEnumerator и MoveNext.AsEnumerable будет получать одну «страницу» исходных данных, которая не является полной коллекцией.Поэтому, если вы используете только 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(),
        })

В следующих инструкциях вы будете использовать локальные функции, такие как getConditions(), поэтому выбранные данные должны бытьперенесено в локальную память на страницу:

Продолжите оператор LINQ:

        .AsEnumerable()

Следующее необходимо, только если вам нужно вернуть извлеченные данные в типизированном объекте.Если вы используете данные только в этом блоке кода, нет необходимости преобразовывать их в новую точку данных:

        // continue the linq statement: put the transferred data in a type.
        .Select(fetchedData => new DataPoint
        {
            Description = fetchedData.Description,
            EffectiveDate = fetchedData..EffectiveDate,
            DataPointStates = fetchedData.DataPointStates,
            ...
            ConstraintFilters = getConditions(), 
        });

        // note: the result is an IEnumerable! no need to convert it to IQueryable
        // because an IEnumerable can do much more than an IQueryable
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...