So what object-to-object mapper (OOM) is? In the simplest case this is an API allowing to transform objects of types T1, T2, ... TN to objects of type T1`, T2`, ... TN` using pre-defined transformation rules (mappings). An example of such a simple API is e.g. AutoMapper (I recommend you to study its description before reading further).
On the other hand, I don't believe into such a simplicity ;) AutoMapper, as well as many similar mappers resolve just a single listed problem: forward-only mapping. I'd like such a tool handles few more cases:
- It must be able to compare two T` graphs, identify the changes made there, and apply them to T graph. It must understand object keys, versions and removal flags while doing this.
- It must be able to transform IQueryable<T> to IQueryable<T`>.
// Creating mapper. Let's think it is already configured. var mapper = ...; // Transform a single object var personDto = (PersonDto) mapper.Transform(person); var personDtoClone = Cloner.Clone(personDto); personDto.Name = "New name"; // Applying changes to the original object mapper.Update(personDtoClone, personDto); // Transforming the queryable var personDtos = (IQueryable<PersonDto>) mapper.Transform(Query<Person>.All); // Nothing has happened yet: we just provided a queryable // that can be invoked later // Here we're actually transforming the queryable to // the original one, executing it, transforming its // result back to PersonDto objects and returning them. var selectedPersons = ( from p in personDtos where p.Name == "Alex" select p).ToList(); var selectedPersons2 = ( from p in personDtos where p.Name == "Sergey" select p).ToList(); // Merging two lists of objects! // This is possible, because we aware about keys. // Conflicts are detected, because we aware about versions :) selectedPersons = mapper.Merge(selectedPersons, selectedPersons2) var selectedPersonsClone = Cloner.Clone(selectedPersons); selectedPersons[0].Name = "Ivan"; // Applying changes to the original objects mapper.Update(selectedPersonsClone, selectedPersons);As you see, this solution allows us to solve the whole bunch of problems:
- You can deal with POCO objects, your own DTOs - anything you want. There are no any special requirements.
- This is simply ideal for SOA: Astoria (ADO.NET Data Services), .NET RIA Services, WCF, etc.
- You may have as many of such mappings as you want. E.g. one per each particular client-side API ;)
- You can use LINQ for your DTOs - that's simply a dream ;) Btw, writing such a translator must be really a peace of cake (there are always one-to-one mappings).
- You shouldn't sacrifice all the benefits our Entity\Structure\EntitySet objects provide - I mean change tracking, lazy loading, auto transactions, validation, etc.!
public sealed class SoaContext : MappedStorageContext
{
[MapTo(typeof(Person)]
public IQueryable<PersonDto> Customers { get; }
// We can implement it by standard way using PostSharp ;)
[MapTo(typeof(Order)]
public IQueryable<OrderDto> Orders { get; }
public override void Initialize()
{
// Executed just once per each type!
// Only complex mappings are here.
// 1-to-1 field mappings are defined automatically.
// Type mappings are recognized from above properties.
Map<Customer, CustomerDto>(
c => c.Order.Total,
dc => dc.OrderTotal);
Map<Customer, CustomerDto>(
c => c.Orders,
dc => dc.Orders.Where(
o => o.Date.Year==DateTime.Now.Year)
}
public SoaContext(Session session)
: base(Session)
{
}
}
Note that this is EF-like context, that can be shared e.g. via ADO.NET Data Services API. Moreover, it provides Update & Merge methods allowing to update original objects or merge state changes - recursively.
I used MappedStorageContext here, which is "pre-tuned" for dealing with our own objects - e.g. it returns Query<T>.All for any auto property of mapped IQueryable<T> type and it is aware about Session. But it should be inherited from general MappedContext allowing you to map any objects as you like in similar fashion.
I hope this fully explains why I believe ORM must not care too much about supporting POCO and quite flexible mapping. These problems are solved by described OOM layer much better. Moreover, if they're resolved at different layer, the code of your Entities becomes more convenient and simple, because ORM can provide standard infrastructure for them.
- In many cases (e.g. in web applications or simple services) you don't need POCO/DTO at all. All you need here is ability to deal with persistent entities using fast, simple and convenient API. Moreover, this API must be ideal for describing BLL rules. That's exactly what DO is designed for.
- If you must maintain disconnected state for long-running transactions, upcoming DisconnectedState (and later - sync) will handle this gracefully. This problem significantly differs from DTOs - e.g. having on-demand downloading capability is quite desirable here.
- Everything else (SOA, WCF serialization, etc.) is covered by this hard and fast solution.
P.S. When this mapper will appear in DO4? Quite likely, we'll start working on it right after upcoming v4.1 update.
8 comments:
Will the mapper support optimizations (automic prefetching) for this reporting scenario?
Map.Customer<CustomerDto>(
c => c.Orders.Total
c => c.Orders.Where(x=>o.Year = DateTime.Now.Year).Total
)
var customers = (IQueryable<CustomerDto>)
mapper.Transform(Query<Customer>.All);
It's not clear to what c.* are mapped (in my example this was clear), but I understood the idea.
Yes, we'll think how to implement preloading of every mapped property and expression efficiently.
This one would be great feature. Lightspeed claims to have one but it is not what i expected, looking forward for this...
I also think so. I hope we'll be able to show mainly working version of described stuff in November.
I saw in google code that O2O just started to implement, is there any time-frame when it can be finished?
Most likely beginning of February. There are some chances that its preview be available in preliminary builds of v4.2 (end of January).
Btw, the task was started in December.
Aha i see it, its starts in december, i was mistaken by google code updates revision pushed by you.
Yep, likely ;)
Btw, pushes are always from me for now: sync code is running from my Google account. So today there were really some changes related to O2O, but this part appeared much earlier.
Post a Comment