Search code examples
entity-framework-5odatabreeze

Breeze doesn't expand TPH entities correctly


Breeze doesn't expand TPH entities correctly. When using expand in breeze if you are using TPH expand will only work for the first entity, the others properties will be null. If I change the entity not to use inheritances it works fine. I've also tested returning each entity separately in an expand query that also worked fine.

//client side code

        var getResidentById = function (id, obs) {

            var query = EntityQuery.from('Residents')
                .where('id', '==', id)
                .expand('user, currentUnit, leases, leases.unit, leases.leaseStatus');

            return manager.executeQuery(query).then(function (data) {
                if (obs) {
                    obs(data.results[0])
                }
            }, queryFailed);
        };

//Controler Endpoint

[HttpGet]
public IQueryable<Resident> 
{
   return _context.Context.UserDetails.OfType<Resident>();
}

//Model

public class UserDetail : EntityBase<int>, IArchivable, IHasPhoto, IDeactivatableEntity, IUpdatable
    {
        public bool IsArchived { get; set; }
        public int LastUpdatedById { get; set; }
        public UserProfile LastUpdatedBy { get; set; }
        public DateTimeOffset LastUpdatedDate { get; set; }
        public string PhotoUri { get; set; }
        public bool IsInactive { get; set; }
    }

    public abstract class UserBelongsToApartmentComplex : UserDetail, IBelongsToApartmentComplex
    {

        public int ApartmentComplexId { get; set; }
        public virtual ApartmentComplex ApartmentComplex { get; set; }

        public virtual bool IsInSameComplexAs(IRelatedToApartmentComplex otherEntity)
        {
            return ApartmentComplexId == otherEntity.ApartmentComplexId;
        }
    }

    public class Staff : UserBelongsToApartmentComplex
    {
        public string Title { get; set; }
    }

    public class Admin : UserDetail
    {
        public string AccessLevel { get; set; }
    }

    public class Resident : UserBelongsToApartmentComplex
    {
        public string Pets { get; set; }
        public bool HasInsurance { get; set; }
        public virtual IList<Lease> Leases { get; set; }
        public int? CurrentUnitId { get; set; }
        public virtual Unit CurrentUnit { get; set; }

        public Resident()
        {
            Leases = new List<Lease>();
        }
    }

//response data from sever from endpoint public IQueryable Residents()

[{"$id":"1","$type":"RadiusBlue.Core.Models.Resident, RadiusBlue.Core","Pets":"Sadie, a westie","HasInsurance":false,"Leases":[{"$id":"2","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2012-05-23T00:00:00.000","End":"2013-05-23T00:00:00.000","UnitId":2,"Unit":{"$id":"3","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"B","Floor":2,"ModelName":"Tera","RentAmount":2500.00,"NumberOfBeds":1,"NumberOfBaths":3,"UnitName":"102A","IsInactive":true,"Inhabitants":[],"ApartmentComplexId":1,"ApartmentComplex":{"$id":"4","$type":"RadiusBlue.Core.Models.ApartmentComplex, RadiusBlue.Core","Name":"The Stratford","StreetAddress":"100 S Park Ave","City":"Winter Park","StateId":10,"ZipCode":"32792","PropertyManagementCompanyId":1,"IsInactive":false,"TimeZoneId":"Eastern Standard Time","TimeZone":{"$id":"5","$type":"System.TimeZoneInfo, mscorlib","Id":"Eastern Standard Time","DisplayName":"(UTC-05:00) Eastern Time (US & Canada)","StandardName":"Eastern Standard Time","DaylightName":"Eastern Daylight Time","BaseUtcOffset":"-PT5H","AdjustmentRules":[{"$id":"6","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"0001-01-01T00:00:00.000","DateEnd":"2006-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"7","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":4,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"8","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":10,"Week":5,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}},{"$id":"9","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"2007-01-01T00:00:00.000","DateEnd":"9999-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"10","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":3,"Week":2,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"11","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":11,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}}],"SupportsDaylightSavingTime":true},"Users":[{"$ref":"1"}],"Groups":[],"IsArchived":false,"ApartmentComplexId":1,"Id":1},"Id":2},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":4,"LeaseStatus":{"$id":"12","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Lost","Id":4},"Id":1},{"$id":"13","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2013-05-24T00:00:00.000","End":"2014-05-24T00:00:00.000","UnitId":1,"Unit":{"$id":"14","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"A","Floor":2,"ModelName":"Aqua","RentAmount":2000.00,"NumberOfBeds":2,"NumberOfBaths":1,"UnitName":"101A","IsInactive":true,"Inhabitants":[{"$ref":"1"}],"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":1},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":1,"LeaseStatus":{"$id":"15","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Active","Id":1},"Id":2}],"CurrentUnitId":1,"CurrentUnit":{"$ref":"14"},"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":3,"User":{"$id":"16","$type":"RadiusBlue.Core.Models.UserProfile, RadiusBlue.Core","UserName":"[email protected]","FirstName":"Vishal","LastName":"Jiawon","Age":27,"PhoneNumber":"123 456 7890","IsInactive":false,"UserDetail":{"$ref":"1"},"GroupMembers":[],"MaintenanceRequests":[],"Id":3},"IsArchived":false,"LastUpdatedById":1,"LastUpdatedDate":"0001-01-01T00:00:00.000+00:00","IsInactive":false,"CreatedById":1,"CreatedDate":"0001-01-01T00:00:00.000+00:00"}]

Solution

  • I do not doubt that there is a bug in BreezeJS somewhere.

    I can report that, at least as of v.1.3.4, Breeze can expand multiple navigation properties of a TPH class ... and not just on the first entity returned.

    I just modified the "can navigate to AccountType eagerly loaded with expand" test in inheritanceTests.js in DocCode so that (a) it also expands the Status navigation and (b) the tests are performed on the 3rd entity returned rather than the 1st.

    The query is something like this:

    var em = newEm(); // clean, empty EntityManager
    return EntityQuery.from('bankRootTPHs').take(3)
        .expand('AccountType, Status'))
        .using(em).execute().then(success).fail(handleFail);
    
    ...
    function success(data) {
        var entity = data.results[data.results.length-1]; // get the last one (the 3rd)
        var type = data.query.entityType.shortName;
    
        if (!entity) {
            ok(false, "a query failed to return a single " + type);
    
        }
        // more tests
        // I just set a breakpoint and inspected 
        // entity.accountType() and entity.status()
        // Both returned the expected related entities
    
    }
    
    

    I see that both the related AccountType and the related Status are available from the entity.

    So something else is wrong.

    Questions about your Example

    First I am compelled to observe that you have a lot of expands. I count 5 related entities. That can hurt performance. I know we're not talking about that but I'm calling it out.

    Second, the super class UserDetail is concrete but the intermediate derived class UserBelongsToApartmentComplex is abstract. You have inheritance class hierarchies that go concrete/abstract/concrete. The queried type, Residents is one such class. And a class at every level maps to the "UserDetail" table, yes?

    I'm pretty sure we didn't test for that scenario ... which is pretty uncommon. I wasn't even sure that worked! For now I have to take your word for it that EF allows such a construct.

    It would seem that BreezeJS is confused about it. We'll take a look.