Is there a way to map nested collections using ValueInjecter? - c#

I have an EF data structure which has data nested two levels deep, it looks roughly like this:
class Country {
string Name {get; set;}
ICollection<State> States {get; set;}
}
class State {
string Name {get; set;}
ICollection<County> Counties {get; set;}
}
class County {
string Name {get; set;}
}
Or: a country has states, and each state has counties.
I've simplified much of the remote facade for this example so my DTO models are effectively the same structure here. Using ValueInjector I'd like my code to look something like.
var dbCountry = _dbContext.Countries.Find(id);
var dtoCountry = new DTO.Country();
dtoCountry.InjectFrom<FlatLoopInjection>(dbCountry);
foreach (var dbState in dbCountry.States) {
var dtoState= new DTO.State();
dtoState.InjectFrom(dbState);
dtoState.Counties = dbState
.Counties
.Select(m => new DTO.County().InjectFrom(m))
.Cast<DTO.County>()
.ToList();
dtoCountry.States.Add(dtoState);
}
The question is: is there a way to to this without the foreach loop? Not that I'm anti-loop (and I'd love to start a holy war on this with the trolls), but this seems to mixing two patterns together (linq and loop).
Thanks.

Related

Dynamically mapping nested objects with CSVHelper

I'm using CSVHelper (thanks Josh Close) to read a CSV file which works great. I'm now trying to use it to map that file to some internal classes; however, the CSV's I'm mapping vary by customer but all need to map to my internal classes. I need to allow the customer to define how the CSV maps to my POCO objects.
I'm storing the customer defined mappings as Dictionary<string,int> -- ["Firstname", 20],["Lastname",21],["Address.Line1",30], ["Address.Line2",31], etc.
I have a dynamic map class that works for dynamically mapping based on a given mapping at runtime. My problem lies in dealing with reference type properties. Here is what I have so far.
POCO Classes
public class Client
{
public int Id {get; set;}
public string Firstname {get; set;}
public string Lastname {get; set;}
public Address Address {get; set;}
...
}
public class Address
{
public string Line1 {get; set;}
public string Line2 {get; set;}
public string City {get; set;}
...
}
Based on a few posts that I've run across here and here, I came up with the following that uses a defined mapping to map a CSV dynamically.
Dynamic Map
public class BaseCSVMap<T> : ClassMap<T> where T : class
{
public void CreateMap(Dictionary<string,int> mappings)
{
foreach(var mapping in mappings)
{
var propname = mapping.Key;
var csvIndex = mapping.Value;
var member = typeof(T).GetProperty(propname);
Map(typeof(T), member).Index(csvIndex);
}
}
}
Using Dynamic Map
var id = 2; //Customer 2
var mappings = dataContext.Mappings.Where(m => m.id = id); //Get customer 2's map
using(var reader = File.OpenText(#"c:\temp\testfile.csv"))
{
var csv = new CsvReader(reader);
csv.Configuration.HasHeaderRecord = true; //hardcoded for now
var map = new BaseCSVMap<Client>();
map.CreateMap(mappings);
csv.Configuration.RegisterClassMap(map);
var records = csv.GetRecords<Client>();
}
I added the following in my BaseCSVMap<T> class, which works great if all my reference properties are strings, but doesn't work so well when a property is something else.
var member = typeof(T).GetProperty(propname);
//New code
//Mapping would look like ["Address.Line1",78]
if(member.GetType().IsClass)
{
string exp = $"c.{propname}";
var p = Expression.Parameter(typeof(T), "c");
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new [] {p}, null, exp);
Map((Expression<Func<T,string>>)e).Index(csvIndex);
}
I also looked for a way to take advantage of the Reference mapping that CSVHelper makes available, but was unable to figure out how to do that in a dynamic fashion.
Looking for some guidance in how to accomplish defining a dynamic map for a reference type with CSVHelper.

Inisght.Database Auto Repository and Custom Column Mapping

I am trying to implement Insight.Database in a project, and have run into a brick wall trying to utilize the automatic interface implementation AND mapping object properties to odd column names in the database.
I have the following structure...
class Employee
{
string EmployeeCode {get; set;}
string Name {get; set;}
}
class Vacation
{
int VacationId {get; set;}
DateTime VacationDate {get; set;}
string EmployeeCode {get; set;}
string ApprovedByEmployeeCode {get; set;}
Employee Employee {get; set;}
Employee ApprovedByEmployee {get; set;}
}
My database looks like....
Table: Employees (EmployeeCode, [Name])
Table: Vacations (VacationId, VacationDate, EmployeeCode, ApprovedByEmployeeCode)
View: VacationsView (so I don't have to keep writing [and changing]
the same SELECT over and over)
SELECT v.VacationId, v.VacationDate, v.EmployeeCode, v.ApprovedByEmployeeCode, e1.EmployeeCode AS CreatedByEmployeeCode, e1.[Name] AS CreatedByName, e2.EmployeeCode AS ApprovingEmployeeCode, e2.[Name] AS ApprovingName
FROM Vacations v
INNER JOIN Employees e1 ON v.EmployeeCode = e1.EmployeeCode
INNER JOIN Employees e2 ON v.ApprovedByEmployeeCode = e2.EmployeeCode
Stored Procedure: GetAllVacations
SELECT * FROM VacationsView
Finally, with Insight.Database, I am trying to have an interface that will autopopulate my objects and tell it how to use the different column names from my stored procedure for the "employee" properties.
public interface IMyRepository
{
IList<Vacation> GetAllVacations();
}
....
var repo = conn.As<IMyRepository>();
return repo.GetAllVacations();
This works (as in doesn't error) and all the properties of my vacation are correctly mapped, but my two "employee" properties are null (as expected because the column names don't line up to the property names of an employee object). What I can't figure out is how to tell Insight "Use CreatedBy.." fields to build the "Employee" property and "Approving..." fields to build the "ApprovedByEmployee" property.
I have been able to accomplish it using OneToOne with a callback and columnOverride and then use a standard Query(). I.E..
var vacationStructure =
new OneToOne<Vacation, Employee, Employee>(
callback: (vacation, createdBy, approvedBy) =>
{
vacation.Employee = createdBy;
vacation.ApprovedByEmployee = approvedBy;
}, columnOverride: new ColumnOverride[]
{
new ColumnOverride<EmployeeModel>("CreatedByEmployeeCode", "EmployeeCode"),
new ColumnOverride<EmployeeModel>("CreatedByName", "Name"),
new ColumnOverride<EmployeeModel>("ApprovingEmployeeCode", "EmployeeCode"),
new ColumnOverride<EmployeeModel>("ApprovingName", "Name")
});
....
var results = await conn.QueryAsync("GetAllVacations", new {employeeCode}, Query.Returns(_vacationStructure));
However, I'm really trying to utilize the auto interface capabilities of Insight.
Is what I'm trying to do possible?
Assembling repeated child objects isn't something that's currently done automatically, and the interface implementation doesn't give you the right hooks to override the behavior.
Some options:
A. Change the shape of your result set to return the employees as a list with properties.
B. If the classes aren't sealed, derive from Employee so Insight can differentiate between the classes:
public class AssigningEmployee : Employee {
public string AssigningName { get { return Name; } set { Name = Value; } }
...
}
These solutions are all meh. The whole point of Insight.Database is to just work without a lot of extra work. So...
I opened a github issue to track this:
https://github.com/jonwagner/Insight.Database/issues/384

automapper with runtime mapping configuration

In my ASP.NET MVC application I need to implemenet mapping from one object to another with some kind of UI for mapping configuration in runtime, so the user can define mapping "on the go". Is there any libraries that supports such functionality?
Description
This is objects in my application. I need to somehow allow user to configure mapping of this objects via UI during application runs. For exmaple some kind of page in my application where user will be able to define mapping in simple way like so map Amout of OrderDTO to Order Qty and later without application recompile change this mapping for exmaple for ExactAmmount
//Object in DAL
public class Order
{
public int Id {get; set;}
public string Name {get; set;}
public decimal Qty {get; set;}
//Lots of other fields
}
//Object from XSD generation (for example)
public class OrderDTO
{
public int Id {get; set;}
public string Description {get; set;}
public decimal Ammout {get; set;}
public decimal VAT {get; set;}
public decimal ExactAmmount {get; set;}
//Lots of other fields
}
Note: for legacy reasons I based this answer on AutoMapper 4.2.1 instead of the current 5.x version. The overall approach should be similar with the new version.
It is possible to create different mapping configurations and different mappers within a program. Also, it is possible to create member mappings by member names (string) instead of lambda expressions. However, some static type information is still necessary (as far as my example goes).
See the following example of a profile, that prepares a custom mapping based on property names:
class MemberProfile : Profile
{
private string from;
private string to;
public MemberProfile(string from, string to)
{
this.from = from;
this.to = to;
}
protected override void Configure()
{
this.CreateMap<Order, OrderDTO>()
.ForMember(to, c => c.MapFrom<decimal>(from));
}
}
This could be extended to support different source property types and a collection of custom mappings instead of a single one.
Usage example:
var order = new Order() { Id = 1, Name = "Test", Qty = 0.5m };
var conf1 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "Ammout")));
var conf2 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "ExactAmmount")));
var res1 = conf1.CreateMapper().Map<OrderDTO>(order);
var res2 = conf2.CreateMapper().Map<OrderDTO>(order);
For res1, Qty is mapped to Ammout and for res2, Qty is mapped to ExactAmmount. Since the difference is described as string property names, it should be possible to let the user influence this configuration.

How do I translate complex objects in ServiceStack?

Suppose I have two objects:
class Order
{
string Name {get; set;}
Customer Customer {get; set;}
Item[] Items {get; set;}
}
and
class OrderDTO
{
string Name {get; set;}
CustomerDTO Customer {get; set;}
ItemDTO[] Items {get; set;}
}
If I receive an object orderDTO that is fully populated and do orderDTO.TranslateTo<Order>() the result will only have Name populated, not Customer or Items. Is there a way to do a recursive translation or the only option is to translate Customer and each of the Items manually?
I would wrap this in a re-usable extension method, e.g:
public static OrderDTO ToDto(this Order from)
{
return new OrderDTO {
Name = from.Name,
Customer = from.ConvertTo<CustomerDTO>(),
Items = from.Items.Map(x => x.ConvertTo<ItemDTO>()).ToArray(),
}
}
Which can then be called whenever you need to map to an Order DTO, e.g:
return order.ToDto();
Some more examples of ServiceStack's Auto Mapping is available on the wiki.
ServiceStack's party-line is if your mapping requires more than the default conventional behavior that's inferable from an automated mapper then you should wrap it behind a DRY extension method so the extensions and customizations required by your mapping are cleanly expressed and easily maintained in code. This is recommended for many things in ServiceStack, e.g. maintain un-conventional and complex IOC binding in code rather than relying on an obscure heavy-weight IOC feature.
If you prefer it, you can of course adopt a 3rd party tool like AutoMapper.
You are going to have to handle complex mapping explicitly yourself. Here are some unit tests from ServiceStack src that show how complex types are currently handled.
You can see that the the Car object is serialized into JSON.
var user = new User() {
FirstName = "Demis",
LastName = "Bellot",
Car = new Car() { Name = "BMW X6", Age = 3 }
};
var userDto = user.TranslateTo<UserDto>();
Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName));
Assert.That(userDto.LastName, Is.EqualTo(user.LastName));
Assert.That(userDto.Car, Is.EqualTo("{Name:BMW X6,Age:3}"));
I agree with Trust Me - I'm a Doctor that Automapper is worth using. The built in Translating was designed to reduce dependencies.

RavenDB SelectMany alternative

Am building a Photo gallery using Nancy and RavenDB
I have my model classes as below:
[JsonObject(IsReference=true)]
public class Album
{
public Album()
{
this.Photos = new List<Photo>();
this.Tags = new List<String>();
}
public String Id {get; set;}
public String Name {get; set;}
public String Path {get; set;}
public DateTime ModifiedDate{get; set;}
public IList<String> Tags {get; set;}
public IList<Photo> Photos {get; set;}
}
public class Photo
{
public String Id {get; set;}
public String Name {get; set;}
public String Path {get; set;}
public IList<String> Tags {get; set;}
public Album Album {get; set;}
public DateTime ModifiedDate{get; set;}
public bool IsPrivate{get; set;}
}
And my attempt at map reduce index on Photo->Tags:
public class TaggedPhotosIndex:AbstractIndexCreationTask<Album, TaggedPhotos>
{
public TaggedPhotosIndex()
{
Map = albums =>
from a in albums
from p in a.Photos
from t in p.Tags
select new TaggedPhotos
{
Tag = t,
PhotoIds = new List<String> {p.Id}
};
Reduce = results =>
from result in results
group result by result.Tag into agg
select new TaggedPhotos
{
Tag = agg.Key,
PhotoIds = agg.SelectMany(a => a.PhotoIds).ToList()
};
}
}
public class TaggedPhotos
{
public String Tag {get; set;}
public IList<String> PhotoIds {get; set;}
}
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have atleast one matching tag.
RavenDB doesn't allow SelectMany in the query and am out of ideas.
Found a solution (using LuceneQuery # Workaround for selectmany in ravendb using client api) , but looking forward for any other alternatives.
There are some issues with your document structure that are going to make your query difficult.
Queries are designed to return whole documents. You are asking for a partial result from the document. That can be done, but only if you store all fields in the index and project the results from there. You give up the ACID guarantees of the document store, which is one of RavenDB's strongest features.
You have a reference from Photo back to Album which would normally be embedded, causing a circular reference, except you are setting [JsonObject(IsReference=true)] to avoid that. This may work for basic serialization within a single document, but it is meaningless when it comes to referencing a whole document back from a projected index value. You've set up your own "chicken & egg" problem.
You still are missing basic functionality one would expect from this problem domain. Specifically, you should be able to load a single photo without having to load the whole album.
I strongly suggest that you put photos in their own documents. Then you can have much simpler indexes and avoid the circular references. In DDD terms, both Photo and Album are aggregates. And a DDD aggregate == a RavenDB document. You are currently modeling the Photo as an entity that is not an aggregate, but then you are asking for operations that one would only do against an aggregate - like the search.
If you were asking only "give me all albums that contain a photo tagged with one of these tags", then you would be ok. But you're not asking for the albums, you're asking for the photos.
Assuming you did model it that way, Brett's answer is close - but has some extraneous stuff. Here is a unit test showing a full implementation. Posting on gist since it does not directly address the original question. https://gist.github.com/4499724
This one took me a while but I verified the solution below is working.
First, modify your Index and index result as shown below.
public class TaggedPhotosIndex:AbstractIndexCreationTask<Photo, TaggedPhotos>
{
public TaggedPhotosIndex()
{
Map = photos =>
from p in Photos
from t in p.Tags
select new TaggedPhotos
{
Tag = t,
PhotoId = p.Id
};
}
}
public class TaggedPhotos
{
public string Tag {get; set;}
public string PhotoId {get; set;}
}
Now that the index is created, here is how you query against it to get your photos.
var tagsToSearch = new List<string>(){"test1", "test2", "test3"};
var photoIds = documentSession.Query<TaggedPhotos, TaggedPhotoIndex>()
.Customize(x => x.Include<Photo>(p => p.Id))
.Where(x => x.Tag.In(tagsToSearch))
.Select(x => x.PhotoId)
.Distinct()
.ToArray();
// This doesn't actually make a second call as the photos are already loaded
// in the document session
var photos = documentSession.Load<Photo>(photoIds);
Based on the requirement
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have
at least one matching tag.
I didn't see how album really tied into it all

Categories

Resources