1

I want to join multiple tables in Entity framework, Query against that.

Want to assign output of my query to a complex object which don't have any table.

I am getting below error, when i tried to do.

EntityType 'ConstraintFilter' has no key defined. Define the key for this EntityType. ConstraintFilters: EntityType: EntitySet 'ConstraintFilters' is based on type 'ConstraintFilter' that has no keys defined.

How to overcome this??.

Code

[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();
    }
4
  • which version of EF, 6? and show your code Commented Sep 26, 2018 at 18:33
  • private List<ConstraintFilter> getConditions() { List<ConstraintFilter> list=new List<ConstraintFilter>(); list.Add(new ConstraintFilter()); return list; }. public class ConstraintFilter { public string Type { get; set; } public string Name { get; set; } public string Operator { get; set; } public string Value { get; set; } } Commented Sep 26, 2018 at 18:50
  • Added main code to question description. I want to add a new object "ConstraintFiletr" to it. Commented Sep 26, 2018 at 18:51
  • If you don't have a table then you cant do this .Include(ent=>ent.ConstraintFilters) or you need to define a key mapping for it (based on the error message) Commented Sep 26, 2018 at 19:05

1 Answer 1

1

Alas you forget to show us the class DataPoint, hence I can only give suggestions to help you solve your problem.

From your code I see that every DataPoint has several one-to-many relations (or many-to-many).

Because you use a plural noun for property ConstraintFilters, it seems that every DataPoint has zero or more ConstraintFilters. These ConstraintFilters are stored in a separate table:

class DataPoint
{
    public int Id {get; set;} // primary key
    ...

    public virtual ICollection<ConstraintFilter> ConstraintFilters {get; set;}
} 

In entity framework non-virtual properties represent columns in your database table; the virtual properties represent relations between the tables (one-to-many, many-to-many, etc)

However, from your title and your error, it seems that ConstraintFilters is one (complex) object, that are columns in your DataPoints table.

class DataPoint
{
    public int Id {get; set;} // primary key
    ...

    public ConstraintFilter ConstraintFilters {get; set;}
} 

If that is the case: the cause is in your .Include(ent=>ent.ConstraintFilters). Don't use it. Just access the properties you plan to use in your query, and the values will be fetched.

Advice: stick to entity framework code-first conventions, this enhances readability of your code. In your case: if your DataPoint has one (complex type) ConstraintFilter don't use the plural ConstraintFilters.

Problems in your query

From your collection of DataPoints you seem to want to select certain DataPoints and query some (or all) of its properties.

In your query I see several problems.

You fetch way more data than you actually use

One of the slower parts of a database query is the transfer from the selected data from your Database Management System to your local process. Hence it is wise to limit the data that is to be transferred.

Every DataPoint has a primary key, probably in a property Id. Every DataPoint has zero or more DataPointEnumerations. Every DataPointEnumeration belongs to exactly one DataPoint (one-to-many). For this the DataPointEnumeration has a foreign key DataPointId, which has the same value as the DataPoint.Id that it belongs to.

If you query a DataPoint with its 1000 DataPointEnumerations you'll know that every DataPointId of every DataPointEnumeration of this DataPoint has the same value, namely the value of DataPoint.Id: what a wast to transfer this same value over and over again.

When querying data do not use Include. Use Select instead, and Select only the properties you actually plan to use. Only use Include if you plan to change and save the fetched included data.

After ToList() you continue concatenating LINQ statements

As long as you keep your data IQueryable, no query is performed. Concatenating LINQ statements will only change the IQueryable.Expression.

The ToList will execute the query and transfer the data to your local process. After this you make it an IQueryable again.

Imagine what happens in the following code:

IQueryable<DataPoint> dataPointQuery = YourProcedure() // the procedure that return your query
DataPoint firsDataPoint = dataPointQuery.FirstOrDefault();

First your ToList fetches all DataPoints that match your Where to local memory, then you only take the first one and discard all other fetched DataPoints: what a waste of processing power.

Keep your IQueryable and IQueryable as long as possible, thus allowing your users to concatenate LINQ statements without actually executing the IQueryable.

IQueryable and IEnumerable

The only reason to perform linq statements after you transferred the data to local memory is because you need to call local functions like you do in ConstraintFilters = getConditions() This statement can't be translated into SQL. This is probably the reason why you added ToList() in your query.

If you start enumerating an AsEnumerable, either implicitly using ToList(), FirstOrDefault(), Any(), foreach, or implicitly using GetEnumerator and MoveNext, an AsEnumerable will fetch only a page of the query, not the complete data.

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

The FirstOrDefault will internally GetEnumerator and MoveNext. The AsEnumerable will fetch one "page" of source data, which is not the complete collection. So if you only use FirstOrDefault(), more than one source item is fetched, but not all, which is a bit more efficient. Only if you enumerate over more items than fits one page, the next page is queried from the database.

Putting the advices together

  • No Include, but Select to fetch only the used data
  • No ToList but as Enumerable to fetch data per page instead of all data
  • Return IEnumerable, after all: the data is in local memory, IEnumerable can do more than 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(),
        })

In the next statements you will be using local functions like getConditions(), hence the selected data must be transferred to local memory per page:

Continue the LINQ statement:

        .AsEnumerable()

The following is only needed if you need to return the fetched data in a typed object. If you use the data only in this code block, there is no need to convert it to a new DataPoint:

        // 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
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.