I have been searching for a way to join 2 distantly related domain models into one view model with no luck.
I am working on an existing application and have been asked to add a field to a result of fax log search. My controller is returning a viewModel, and I am just wanting to add an additional field to it. Which sounds like it should be an easy task.
Background Info:
This is the original viewModel:
public class VOEIFaxLogSearchListViewModel
{
public DateTime DateTimeAdded { get; set; }
public string Processor { get; set; }
public string FaxStatusCode { get; set; }
public string VendorOrderID { get; set; }
public string FromFaxNumber { get; set; }
}
I want to add an additional field to this viewmodel:
public string CustomerName { get; set; }
The way that the application is designed, a stored procedure is called to return a dataset of the search results. (I won't the search method (GetFaxLogSearchResult) or its SQL call as it isn't necessary)
var resultFaxDS = vOEIDAO.GetFaxLogSearchResult(startDate,endDate,userName,faxType);
The resulting DataSet is then converted to a DataTable.
DataTable faxTable = resultFaxDS.Tables[0];
A For loop interates through each of the result records and puts them into a Domain Model named FaxModel. Which is mapped with Automapper to the viewModel named FaxLogSearchListViewModel.
for (int i=0; i<faxTable.Rows.Count;i++)
{
var row = faxTable.Rows[i];
var faxLogModel = vOEIDAO.DRToFaxModel(row);
faxViewModel.Add(Mapper.Map<FaxModel,FaxLogSearchListViewModel>(faxLogModel));
}
}
return faxViewModel;
}
Here is what I have done so far to add this result field:
1) added the new property to the view model.
2) modified stored procedure that pulls back the search results so it returns CustomerName in the dataset
The dilemna:
The method adding each row of the dataset into the Domain model (DRToFaxModel) is doing just that... it is populating a domain model(FaxModel). The field that I want to add isn't in the Domain model. As a result, I don't want to add a field to the domain model if it doesn't belong to the concrete class.
Here is the domain model and the method used to populate it with each row from the search results:
public class FaxModel
{
public int FaxID { get; set; }
public int FaxStatusID { get; set; }
public string ToFaxNumber { get; set; }
public string FromFaxNumber { get; set; }
public DateTime DateTimeAdded { get; set; }
public string FaxStatusCode { get; set; }
public string Processor { get; set; }
public string VendorOrderID { get; set; }
}
public FaxModel DRToFaxModel(DataRow dr)
{
FaxModel voObj = new FaxModel();
voObj.FaxID = GetVOInt(dr["FaxID"]);
voObj.FaxStatusID = GetVOSmallInt(dr["FaxStatusID"]);
voObj.ToFaxNumber = GetVOStr(dr["ToFaxNumber"]);
voObj.FromFaxNumber = GetVOStr(dr["FromFaxNumber"]);
voObj.DateTimeAdded = GetVODateTime(dr["DateTimeAdded"]);
voObj.FaxStatusCode = GetVOStr(dr["FaxStatusCode"]);
voObj.Processor = GetVOStr(dr["Processor"]);
voObj.VendorOrderID = GetVOStr(dr["VendorOrderID"]);
//Cant add CustomerName to the model without modifying the FaxModel domain model.
//Shouldn't do that because it is a domain model.
//CustomerName is in the CustomerModel domain Model
// voObj.CustomerName = GetVOStr(dr["CustomerName"]);
return voObj;
}
So currently, my ViewModel with the added CustomerName property is returned with a null for CustomerName.
My domain models are distantly related. In the database the FAX table can be joined joined to the CUSTOMER table but only by joining through an ORDER table. (the FAX table has an orderID field and the ORDER table has a CustomerID field)
So my resulting question is: how do you use autoMapper to map a Fax domain model to a Customer domain model since the 2 domains don't have any common fields to build the relationship without joining through another table?
Or can you map more than 2 tables into 1 viewModel using automapper? how is this done?
What a great question. First of all, it's so refreshing to see someone asking about the proper way to do something, rather that just seeking a quick fix. Second, the amount of documentation provided is exactly the way SO questions should be written. I wish I could give this more than +1.
That said, since you're essentially asking an architecture question, there aren't any concrete answers, just opinions.
Here's my opinion:
You state the result of the sproc is mapped to a domain model:
However, you've added a return field,
CustomerName
to your sproc which is not part of the domain model. I think that's the heart of your issue.There's a choice to be made here: does this sproc return a domain model or doesn't it?
Right now, my opinion is that it does not represent a domain model anymore, due to the new field, so you should not be trying to map it to a domain model before mapping it to your view model. You need to create a new data type to map this result to, which represents what you are actually getting from the sproc, and map that to your view model.
The alternate option is that this sproc does in fact represent a domain model. If that is the case, you should not be adding a new field to it that is not part of the model. Rather, you'll need to get the
FaxModel
domain objects andCustomerModel
domain objects separately, and assemble your view models from both objects.This is an example of the Single Responsibility Principle, meaning that an object, function, assembly, heck, even a program, should have one purpose. By giving your sproc a return value that both is and isn't a domain model, you're giving it more than one purpose. It would be best to either decide that it represents a
FaxModel
, and accept that the customer name needs to come from another source, or decide that it returns something else, sayCustomerFaxModel
which contains both customer and fax information, and use it as such.To answer your technical question, AutoMapper does allow you to pass an existing target object to the map function in addition to a source object. You can map the target from object A to get some fields, and then pass the already mapped target to
Map()
a second time with a source of object B to map other fields.Always, always, keep asking questions like this and you'll do well.