I'm new to Azure App Service mobile apps. I'm trying to understand my options for using TableController to expose complex domain objects to clients. My goal of using TableController is to take advantage of client-side querying and offline sync.
Table controllers are designed to perform CRUD operations on simple DTOs. So I'm trying to figure out how a complex domain model could be exposed as the sort of DTOs that TableController is designed for.
I've read this post which explains MappedEntityDomainManager. That example shows a fairly simple mapping between DTOs and persistent objects. But what if I want my mapping to be more complex?
For example, let's say I have persistent types like this:
public class Order {
public Customer Customer { get; set; }
public IList<OrderItem> OrderItems { get; }
}
public class Customer {
public string Name { get; set; }
public string TelephoneNumber { get; set; }
}
public class OrderItem { ... }
And I have a table controller declared like this:
public class OrderController : TableController<OrderDto>
Could the OrderDto then look like this?
public class OrderDto {
public string CustomerName { get; }
public string Customer { get; }
public string OrderItems { get; }
}
The mappings would be as follows. The Order.Customer.Name property is flattened into OrderDto.CustomerName. The complete Customer object is serialized into OrderDto.Customer. And the Order.OrderItems list is serialized into OrderDto.OrderItems.
Can this sort of complex mapping be done with MappedEntityDomainManager? If not then how could it be done? I know about leveraging $expand, but I worry that may be an unsupported hack rather than the recommended approach.
The best way to do this is to use Automapper. The blog post was a simple example, but you can do very complex mapping using automapper.
Here's a more complex example: https://github.com/paulbatum/FieldEngineerLite/blob/master/FieldEngineerLite.Service/Controllers/JobController.cs. It's for Azure Mobile Services, but the same concept applies to Azure Mobile Apps. You just need to change the namespaces.
Related
I'm in a position where I need to serialize some complex documents into MongoDb, but I can't change the class definition as I don't have control over the source.
However, we need to ensure that callers can still use Linq, so we need to map the class correclty into MongoDb.
Current there are few issues we're faced with:
The _id_ representation is on a nested class.
There are properties with private setters that need to be serialized/ deserialzied.
The shape of the class looks a little like this:
public class AggregateType : AggregateBase
{
public int IntProperty { get; private set; }
public ComplexObject ComplexObjectProperty { get; private set; }
}
With AggregateBase looking like this:
abstract public class AggregateBase
{
public AggregateDetails Details { get; set; }
}
And finally:
public class AggregateDetails
{
public Guid Id { get; set; }
...other properties
}
On the base class AggregateBase, there is a property called Details which contains the Id of the aggregate, which is a Guid. This Id field needs to be mapped to the ObjectId or _id field within a MongoDb document.
I need to be able to serialize the document, forcing the use of the Details.Id as the _id, and have the private setters serialized too.
I've done this with CosmoDb using a custom JsonContractResolver without issue. But the move to MongoDb has proved a little more complex.
It's worth noting that there are many AggregateType classes, all with a different shape. I'd like to find a generic way of serializing them, without having to write lots of specific mappers if possible - much like we do with CosmoDb.
On top of that, we would need this solution to work with the Linq query provider for MongoDb too.
Ive thought a little about this , the only way I can see this working is if you create matching types that will serve as your POCO for inserting into mongodb. Im going to assume you are using the C# Driver for Mongo.
public class AggregateTypeDocument : AggregateBaseDocument
{
public int IntProperty { get; private set; }
public ComplexObject ComplexObjectProperty { get; private set; }
}
abstract public class AggregateBaseDocument
{
public AggregateDetailsDocument Details { get; private set; }
}
public class AggregateDetailsDocument
{
[BsonId]
public Guid Id { get; private set; }
...other properties
}
You will end up replicating the structure but just be appending Document at the end for this example. By no means do you have to conform to this
Now you can mold your types to be more mongo friendly using various attributes.
The next step would be to either in your repository ( or wherever ) to map the types with class definitions you don't have access to to your new mongo friendly ones.
I would suggest AutoMapper for this or plain old instantiation. Now you should be able to safely operate on the collection. See below example for automapper.
var normalAggregateType = new AggregateType();
var client = new MongoClient("yourconnectionstring");
var db = client.GetDatabase("mydatabase");
var collection = db.GetCollection<AggregateTypeDocument>("myaggregatetypes");
var mongoAggregateType = Mapper.Map<AggregateTypeDocument>(normalAggregateType);
collection.InsertOne(mongoAggregateType);
Hi i'm relatively new to Azure Mobile Apps and Entity Framework so
I've been struggling for a while with the following.
I have the following model on an azure mobile app in .net:
public class Location : EntityData
{
public ProximityRange ProximityRange { get; set; }
public double Rssi { get; set; }
public DateTime TimeOfLocation { get; set; }
}
where ProximityRange is just a container class for two other properties
public class ProximityRange
{
public double Distance { get; set; }
public double Accuracy { get; set; }
}
The controller has a method to get all Locations by a query.
public IQueryable<Location> GetAllLocations()
{
var query = Query();
return query;
}
But when I call the controller I only get a JSON with the TimeOfLocation and the Rssi.
In the DB table of the entity Location I have 3 columns
ProximityRange_Accuracy, ProximityRange_Distance and Rssi
I need to get all 3 properties from the controller GetAllLocations as a JSON Object.
I've tried using $expand=ProximityRange in the query but throws an internal error.
I've also tried declaring ProximityRange as a ComplexType but no luck either.
Would much appreciate someones help.
You cannot (without some difficulty) use complex types (like DbGeography or your ProximityRange) in Azure Mobile Apps models. The OData translation doesn't deal with them.
The best idea is to translate it to a string, then implement a JSON encoder/decoder. The class to look at is a JsonConverter
I've seen this that suggest I can build different views based on user:
different json views for the same entity
However in asp web api, one uses a Model class, I can't just add new properties willy-nilly.
So, for example I may have uri:
http://host/api/products/id
Returning the model:
public class Product{
public string Code { get; set; }
public string Description { get; set; }
}
But for another purpose I want to add more information, suppose this is expensive because it joins other data to build the model, or formats the data in a very specific way:
http://host/api/productsspecial/id
Returning the model:
public class ProductSpecial{
public string Code { get; set; }
public string Description { get; set; }
public decimal Price { get; set; } //assume expensive to look up
}
So obviously I have a way to do this, two different controllers, returning different views on the data. My question is, is this OK or is there a better way?
Anyway I could do this for example: http://host/api/products/id?includeprice=true and use that to return the alternative model? And is that a good idea?
I would suggest
GET /host/api/products/{id}?fields=code,description,price
You should avoid complicating your resource URL in the manner you describe. Every possible configuration of values would need a new name: "productsReallySpecial", etc.
The problem with ?includePrice=true is you then have a parameter for every variable you might want to make optional. Your documentation can list the default return values and the available return values.
I am trying to create a RESTful web service that returns a list of products using ASP.NET MVC4 Web API. Here is my controller class
public class ProductController : ApiController
{
public IEnumerable<Product> GetProducts()
{
WebCatalogContext dbcontext = DatabaseConfig.Instance.Context;
List<Product> plist = dbcontext.Products.ToList();
return plist;
}
}
When I run my service and call the following URL from my browser :/api/Product, I get System.Runtime.Serialization.SerializationException. I looked into my plist object and there is no problem with it.
Here is my data model:
[DataContract(Name = "p")]
[Serializable]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name = "id")]
public int Id { get; set; }
[Required, MaxLength(50)]
[DataMember(Name = "ti")]
public string Title { get; set; }
[Required]
[DataMember(Name = "de")]
public string Description { get; set; }
[Required]
[DataMember(Name = "ph")]
public string PhotoURL { get; set; }
[DataMember(Name = "ca")]
public virtual ProductCategory Category { get; set; }
}
[DataContract(Name="pc")]
[Serializable]
public class ProductCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name="id")]
public int Id { get; set; }
[DataMember(Name="nm")]
public string Name { get; set; }
}
When I remove the reference to ProductCategory from my Product class, all things work just fine. But, when I include it I get the following exception.
Type 'System.Data.Entity.DynamicProxies.Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD' with data contract name 'Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
Any idea about what I am missing?
Regards
Entity Framework has wrapped your POCO with an EF Proxy POCO so it can perform lazy loading - this uses the Virtual attribute to create a 'lazy-loadable' navigation property. I expect that is where the serialization error comes from.
You could make a new class and map the POCO to that - then pass the DTO style class from the controller. I've never returned an EF object directly from the API (I always map to some something else) so I don't know another option.
EF POCO to DTO (data transfer object) is relatively painless if you use a tool like http://valueinjecter.codeplex.com/ or http://automapper.org/
To support Lazy Loading for navigation properties which is declared as virtual, EF will generate the proxies for any models which have navigation properties which leads to this kind of exception.
For very simple application, you can use model from EF as DTOs (if having no navigation properties), but for complex application, you should do separate and differ between DTOs and domain models. It should not be mapping 1:1 between DTO and domain model.
Therefore, in your case, you create more DTO model for Web API layer, it will be fine.
I'm using Entity Framework Code First. The class i'm trying to create contains two collections (of the same type). I'm having problem recovering my respective collections.
My classes look like this:
public class Destination
{
public int DestinationId { get; set; }
public string Name { get; set; }
public List<Lodging> Lodgings { get; set; }
public List<Lodging> Lodgings2 { get; set; }
}
public class Lodging
{
public int LodgingId { get; set; }
public string Name { get; set; }
public Destination Destination { get; set; }
}
I created a new Destination, then I reopened (closed & opened) the database connection. When I retrieve the destination, my collections (dest.Lodgings and dest.Lodgings2) are null. How do I restore the respective collections? If my class only has one collection of a particular type, I could do the following:
var lodgings = context.Lodgings.Where(l => l.Destination.DestinationId == destId).ToList();
I can see that the relationships are maintained in the database schema (Destination_DestinationId1 and Destination_DestinationId2) but I don't seem to be able to get to them.
Any suggestion would be appreciated.
In addition to using Include (as you've discovered) (which loads the related data from the db at the same time the destination is retrieved) you can also retreive the lodgings after the fact. So if you query for the destination and then you want the lodgings, that's possible. One way is called explicit loading where you will use a Load method. The other is with lazy loading, which requires that your classes be set up a particular way and just the mere mention of the Lodgings property will trigger the call to the database to retrieve them.
there's a great blog post on the Ef team blog about the various ways to load related data with DbContext : http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
hth
Julie