1

I am using code first with an existing database, EF5, Web API and Breeze and I havent used any of these techs before. I am writing my own pocos.

I am trying to expose a read only property that requires several table joins to obtain the data. If we were using Web API only, we could just run some sql, populate the property and send some JSON back to the client.

Because we are using EF and breeze this obviously changes quite alot.

For example:

    public class Employee
{
    [Key]
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [NotMapped]
    public string FooBar
    {
        get { return getFooBar(); }
    }

}
    private string getFooBar()
    {
        // Do stuff here
    }

This will send FooBar back to the client in the JSON result but because it is not mapped, and consequently not in the Metadata, I dont seem to be able to use it within Breeze.

I have read articles that say I can do this when using designer based methods (ie edit the edmx file) but how can it be done using code first?

I am aware that I can extend a Breeze entity on the client side but Im not really sure how I would get this value which hasnt been mapped, after Breeze has created all of the entities.

What I really want is to extend my code first entity. I also vaguely understand that this might not be in line with EF ideals but I also struggle with the idea that I dont have the freedom to define what is and what isnt a property of my employee.

I dont need to track changes. I dont need to save. I dont seem to be able the use the EF context provider to join the (many) tables and get the data because the entities for each table dont share a primary key and dont inherit from the same class.

I think this SO post here suggests something similar but once again its for generated classes. Is there a way to do this? Thanks.

Edit
In reply to Wards suggestion I tried a few tests.

My client side constructor:

function Employee() {
    this.DisplayName = ""; // unmapped property  
};

My Controller:

function TestController($scope, $routeParams) {
var manager = new breeze.EntityManager('breeze/employees');

var metadataStore = manager.metadataStore;
metadataStore.registerEntityTypeCtor("Employee", Employee);

var query = new breeze.EntityQuery()
    .from("Employees")
    .orderBy("FirstName");

manager.executeQuery(query).then(function (data) {

    // Check unmapped property name
    var employeeType = metadataStore.getEntityType("Employee");
    var unmapped = employeeType.unmappedProperties;
    alert(unmapped[0].name)     // Returns 'DisplayName'
    alert(employeeType.dataProperties[3].name) // Returns 'DisplayName'

    var prop = manager.metadataStore.getEntityType('Employee').getProperty('DisplayName');
    alert(prop.name) // Returns 'DisplayName'

    var first = data.results[0]
    var fullName = first.DisplayName
    alert(fullName) // Returns empty string                

    $scope.employees = data.results;
    $scope.$apply();

}).fail(function (e) {
    alert(e);
});
};

My Angular:

<div>
<ul>
    <li data-ng-repeat="employee in employees">
        {{employee.DisplayName}}
    </li>
</ul>
</div>

So the property seems to be setup correctly as an unmapped property, but it only returns the empty string. If I change

this.DisplayName = ""; // unmapped property

to

this.DisplayName = "Foo"; // unmapped property

then DisplayName always contains "Foo". The values from the payload are not being applied to DisplayName.

Am I missing something?

Community
  • 1
  • 1
spryce
  • 616
  • 7
  • 14

2 Answers2

0

It's pretty easy on the Breeze client as explained in the Extending Entities documentation topic: you define an unmapped property in a custom constructor and register that constructor.

var metadataStore = myEntityManager.metadataStore;

metadataStore .registerEntityTypeCtor("Employee", Employee);

function Employee () 
  this.FooBar = ""; // unmapped property
};

Now the Breeze metadata includes a definition of the FooBar unmapped property. The server will send a value for FooBar to the client and Breeze will populate that client Employee entity (unmapped) property when it materializes Employee entities from a query.

How you obtain that FooBar property value on the server is up to you. I don't know enough about your app. What you've shown us is a perfectly valid Code First entity definition.

Maybe you're asking an Entity Framework question rather than a Breeze question.

Ward
  • 17,793
  • 4
  • 37
  • 53
  • Thanks. I wasnt aware that Breeze would also pick up the value from JSON. I think this would be a manageable solution but unfortunately that just returns the empty string as defined in the constructor. It seems [others are having this issue as well](http://stackoverflow.com/questions/18011242/unmapped-property-on-the-angular-breeze-spa-template). Is this a bug? Im also using an Angular front end. – spryce Nov 16 '13 at 12:58
  • The name of the property has to match the name in the JSON payload AFTER it has been transformed by the NamingConvention. So look at the JSON payload ... find your FooBar in there first ... then answer the question "what would Breeze say this property is named on the client entity?" ... and give your unmapped property that name. It will work. Breeze has been setting unmapped properties this way for several versions now. – Ward Nov 16 '13 at 20:07
  • There is an example of this in the [DocCode sample](http://www.breezejs.com/samples/doccode "DocCode Sample"). Look in *entityExtensionTests.js* for the test "unmapped property can be set by a calculated property of the server class". – Ward Nov 16 '13 at 20:11
  • The successful test in _entityExtensionTests.js_ has this line `var first = data.results[0];` . The `first` object's properties are not actually properties. They are functions. I'm guessing this is something to do with KO? In my Angular application the properties are properties of the `_backingStore` object within my `first` object. I think that is correct. In regards to naming conventions I am using default and according to the Breeze doc this means the client should reflect the server. So unless Im mistaken this should just be `DisplayName`. I'll add some more code to the original post – spryce Nov 17 '13 at 02:43
  • I've tried `DisplayName`, `displayName` and `displayname`. Still hasnt resolved the issue. – spryce Nov 17 '13 at 22:33
0

One way to get this working has been discussed in this SO answer from CassidyK. Here is the code snippet.

 proto.initializeFrom = function (rawEntity) {
    // HACK:
    // copy unmapped properties from newly created client entity to the rawEntity.
    // This is so that we don't lose them when we update from the rawEntity to the target.
    // Something that will occur immediately after this method completes. 
    var that = this;
    this.entityType.unmappedProperties.forEach(function(prop) {
        var propName = prop.name;
        that[propName] = rawEntity[propName];  // CassidyK 
        //rawEntity[propName] = that[propName]; // Breeze 
    });

    if (!this._backingStore) {
        this._backingStore = { };
    }
};    

I dont know what the side effects of this are. Perhaps one of the Breeze devs can better explain.

It seems this is only a problem when Breeze is configured for Angular.

IE

breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
Community
  • 1
  • 1
spryce
  • 616
  • 7
  • 14
  • I intend to get clarity on this. I have seen this code fragment myself and wondered what is going on (it looks backwards to me too). OTOH, I wrote an "Angular" (read: backingStore ModelLibrary) version of the test I mentioned and it works. So something is different in our environments. I will try to get more info for you soon. – Ward Nov 21 '13 at 07:40
  • There doesnt seem to be another way to do this at the moment so I'll mark this as the answer. We've decided to move forward without Breeze unfortunately. – spryce Dec 05 '13 at 01:13
  • Because of THIS? This, once confirmed, is pretty trivial to fix. Or was there something more substantive? We'd like to know. – Ward Dec 05 '13 at 16:15
  • There were more factors, not all Breeze specific. The existing DB for this project is not ORM friendly. We found a bug in the MySQL connector related to linq query conversion for EF. There was also some discussion over hacking your hack and if its worthwhile hooking _THIS_ particular app up to Breeze manually. We are limited by resources and the existing (messy) app that we are trying extend/improve, so we are just looking at taking a different direction for now. I personally am looking forward to playing with Breeze but it seems I will have to wait for now. Cheers. – spryce Dec 11 '13 at 04:32