AutoMapper doesn't map calculated field to scalar - asp.net-mvc-3

I am trying to separate my MVC3 project into a proper DAL/Domain/ViewModel architecture, but I'm running into a problem with AutoMapper and mapping calculated fields from my domain to my view model.
Here's an example of what I'm trying to do:
Interface
public interface IRequirement
{
int Id { get; set; }
... bunch of others
public decimal PlanOct { get; set; }
public decimal PlanNov { get; set; }
public decimal PlanDec { get; set; }
... and so on
decimal PlanQ1 { get; }
... etc
decimal PlanYear { get; }
... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}
Domain Model
public class Requirement : IRequirement
{
public int Id { get; set; }
... bunch of others
public decimal PlanOct { get; set; }
public decimal PlanNov { get; set; }
public decimal PlanDec { get; set; }
... and so on
public decimal PlanQ1 { get { return PlanOct + PlanNov + PlanDec; } }
... etc
public decimal PlanYear { get { return PlanQ1 + PlanQ2 + PlanQ3 + PlanQ4; } }
... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}
There are also VarianceX properties, i.e. VarianceOct which is calculated as (PlanOct - ActualOct), etc.
My view model looks almost exactly the same, except instead of calculated fields it has the default getter/setter syntax, for example:
public decimal PlanQ1 { get; set; }
My AutoMapper config in Global.asax looks like this:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>();
This works fine on all properties except the calculated ones. None of my calculated fields (i.e. *Q1, *Q2, *Q3, *Q4, *Year, and all the Variance* fields) are actually mapped -- they all show up with the default value of 0.00.
I'm pretty stumped on this, and I'm also a novice at this and AutoMapper, so maybe I missed something. My intuition is that since the property signatures aren't identical (i.e. the domain object has only a non-default getter and no setter, while the view model has default getter and setter) then AutoMapper isn't picking it up. But I also did this:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>()
.ForMember(dest => dest.PlanQ1, opt => opt.MapFrom(src => src.PlanQ1);
And it still resolved to 0. I confirmed this in the debugger as well.
What am I doing wrong?
Thanks in advance.
EDIT 1
After following Wal's advice I ran the test and it worked, so I began working backwards one step at a time, first pasting in the Field1/Field2/Field3 parts into the interface/domain/view model classes and verifying it worked in my controller, then changing one thing at a time. What I found is that, since I am dealing with decimal types, if I hard-code in integer or double values then I get zero, but if I cast to a decimal or use a decimal literal then it works. But only if I manually set them, not if I pull the values from the database.
In other words, this works (i.e. PlanQ1 = 6):
var D = new Requirement { PlanOct = (decimal) 1.0, PlanNov = (decimal) 2.0, PlanDec = (decimal) 3.0 };
var V = Mapper.Map<IRequirement, Details>(D);
And this works:
var D = new Requirement { PlanOct = 1M, PlanNov = 2M, PlanDec = 3M };
var V = Mapper.Map<IRequirement, Details>(D);
But this does not (pulling a single domain object from a repository object, that in turn pulls from SQL Server using Entity Framework):
var D = requirementRepository.Requirement(5);
var V = Mapper.Map<IRequirement, Details>(D);
With the above all I get is 0 for PlanQ1 and PlanYear. I verified that PlanOct = 1, PlanNov = 2, and PlanDec = 3 in the domain object (D). I also verified that the type in all objects, including the EF generated object, is decimal, and the SQL Server type is decimal. I even tried mapping to a created view model, just to rule that out, and I still get 0 for PlanQ1 and PlanYear:
var D = requirementRepository.Requirement(5);
var V = new Details();
Mapper.Map<IRequirement, Details>(D, V);

is PlanQ1 a member of IRequirement ? you have implied it is by your last code snippet but if it isn't then you will get the behavior exactly as you describe.
Consider a simplied example of what you are doing:
public interface IFoo
{
string Field1 { get; set; }
string Field2 { get; set; }
//string Field3 { get; }
}
public class Foo1 : IFoo
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get { return Field1 + Field2; } }
}
public class Foo2
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
Note in this instance I have omitted Field3 from the interface; now when I run the following the mapping fails
[TestMethod]
public void Map()
{
Mapper.CreateMap<IFoo, Foo2>();
var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
var foo2 = new Foo2();
Mapper.Map(foo1, foo2);
Assert.AreEqual("field1field2", foo2.Field3);//fails, not mapped
}
So if I comment in Field3 from IFoo everything works again. Check this simplified example with your code.

Consider #Wal post, Try this Map,
Mapper.CreateMap<IFoo, Foo2>()
.ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));
And
[TestMethod]
public void Map()
{
Mapper.CreateMap<IFoo, Foo2>()
.ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));
var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
var foo2 = new Foo2();
Mapper.Map(foo1, foo2);
Assert.AreEqual("field1field2", foo2.Field3); // True
}

Just realized this was left unanswered so I wanted to close it out. Technically it is unanswered because I couldn't get Automapper to play nice in this scenario for some reason. What I wound up doing is going back and creating a couple of mapping methods inside my repository, one to map a single instance of the DAL object to the IRequirement object, and one to map a collection. Then in the repository instead of calling Mapper.Map I just call my custom mapping methods and it works perfectly.
I still don't understand why this doesn't work, but I've run into a few other classes where Automapper just throws up and I have to manually map at least one or two fields, though Automapper does take care of the rest in those cases.
I'm sure there's something about it I just don't see yet. But in any case, falling back to partial or fully manual mapping was my workaround.

Related

MongoDB c# Deserialization - property not recognized

I'm upgrading from using BsonDocument everywhere, to using deserialization on POCO objects.
Overall the objects are populated with correct values, but i'm having a problem with the code below, values on the EventReferenceEntry.Id property - which are always 0.
Perhaps worth knowing, is that if i remove "BsonIgnoreExtraElements" attribute from the EventReferenceEntry class, i get an error "Element 'i' does not match any field or property of class "
I've also tried setting EventReferenceEntry.Id to Int64 and UInt64, but no difference.
The driver is version 2.7.3, i tried it with a fresh installation of the latest version, but it's the same problem.
The database is a series of events. An event has:
_id = Int64
_t = Int32 (the type of the event)
_r = an array of objects (references to other objects, entities or events, that are relevant.)
C# Code of the POCO objects
[BsonIgnoreExtraElements]
public class EventEntry
{
[BsonElement("_id")]
public ulong Id { get; set; }
[BsonElement("_t")]
public int Type { get; set; }
public DateTime Time { get { return new DateTime((long)Id, DateTimeKind.Utc); } }
[BsonElement("_r")]
public List<EventReferenceEntry> References { get; set; }
}
[BsonIgnoreExtraElements]
public class EventReferenceEntry
{
[BsonElement("i")]
public UInt64 Id { get; set; }
[BsonElement("n")]
public string Name { get; set; }
[BsonElement("a")]
public int Asset { get; set; }
public EventReferenceEntry()
{
}
}
An example database entry
{
"_id" : NumberLong(637684658186492532),
"_t" : 1058,
"_r" : [
{
"n" : "p",
"i" : NumberLong(637662370697662760)
},
{
"n" : "a",
"a" : 1202
},
{
"n" : "o",
"i" : NumberLong(637684655676255124),
"a" : 2934
}
]
}
Found the problem and solution.
A property in the class (in my case EventReferenceEntry) cannot be named “Id” or “id”, I guess that a property named that is assumed to be bound to the _id bson property. It can however be named “ID” or “Ident” or anything else, and the value gets assigned.

MVC 3 Post of Viewmodel with Completex IEnumerable

I have a complex class that is part of a property of a viewmodel. My viewmodel has a wine class property and a wine class has a ICollection property called CaseProductions. The CaseProduction class has several properties as well.
On the create GET event, the NewWineViewModel is instantiated, then it runs a GetCaseProductionDefaults with create a list of CaseProduction classes that have some default values, but are mostly empty.
Now, I originally used razor to do a foreach statement and just pop out my table the way I wanted it. But I've see around that doesn't work to bind this type of IEnumerable back to the viewmodel on POST. I've tried to use the below, but no dice.
EditorFor(m => m.Wine.CaseProductions)
I'm really looking for advise on what the best way to handle this is. Each wine will have a collection of caseproductions, and I want that to bind back to the wine within the viewmodel. Is their some way I can edit the ids of those elements in razor to make sure they bind? What's the best way to handle this one?
viewmodel:
public class NewWineViewModel
{
public Wine Wine { get; set; }
public VOAVIRequest VOAVIRequest { get; set; }
public bool IsRequest { get; set; }
public Dictionary<int, int> BottlesPerCase { get; set; }
public SelectList VarTypes { get; set; }
public SelectList Origins { get; set; }
public SelectList Apps { get; set; }
public SelectList Vintages { get; set; }
public SelectList Importers { get; set; }
}
case production class:
public class CaseProduction
{
public int CaseProductionID { get; set; }
public int WineID { get; set; }
public int CaseProductionSizeID { get; set; }
public int CaseCount { get; set; }
public int CountPerCase { get; set; }
public virtual CaseProductionSize CaseProductionSize { get; set; }
public virtual Wine Wine { get; set; }
}
getting default case productions:
public List<CaseProduction> GetCaseProductionDefaults(vfContext db)
{
//creates blank list of all formats
List<CaseProduction> cp = new List<CaseProduction>();
foreach (CaseProductionSize size in db.CaseProductionSizes)
{
int defaultBottlesPerCase = 1;
switch ((CaseProductionSizeEnum)size.CaseProductionSizeID)
{
case CaseProductionSizeEnum.s187ml:
defaultBottlesPerCase= 24;
break;
case CaseProductionSizeEnum.s375ml:
defaultBottlesPerCase = 12;
break;
case CaseProductionSizeEnum.s500ml:
defaultBottlesPerCase = 12;
break;
case CaseProductionSizeEnum.s750ml:
defaultBottlesPerCase = 12;
break;
default:
defaultBottlesPerCase = 1;
break;
}
cp.Add(new CaseProduction { CaseProductionSizeID = size.CaseProductionSizeID, CountPerCase = defaultBottlesPerCase, CaseProductionSize = size, WineID = this.Wine.WineID });
}
return cp;
}
my razor code for the case production table:
#foreach (vf2.Models.CaseProduction cp in Model.Wine.CaseProductions)
{
<tr>
<td>#cp.CaseProductionSize.Name
</td>
<td>#Html.Raw(#Html.TextBoxFor(m => m.Wine.CaseProductions.Where(c => c.CaseProductionSizeID == cp.CaseProductionSizeID).First().CaseCount, new { #class = "caseCount", id = "txt" + cp.CaseProductionSize.Name }).ToString().Replace("CaseCount","txt" + cp.CaseProductionSize.Name))
</td>
<td>
#Html.DropDownListFor(m => m.Wine.CaseProductions.Where(c => c.CaseProductionSizeID == cp.CaseProductionSizeID).First().CountPerCase, new SelectList(Model.BottlesPerCase, "Key", "Value", cp.CountPerCase), new { #class = "countPerCase", id = "ddl" + cp.CaseProductionSize.Name, name = "ddl" + cp.CaseProductionSize.Name})
</td>
<td class="totalBottleCalc">
</td>
</tr>
}
instantiation of my caseproduction collection:
public ActionResult Create(int ID = 0, int VintUpID = 0)
{
NewWineViewModel nw = new NewWineViewModel();
nw.Wine.CaseProductions = nw.GetCaseProductionDefaults(db);
nw.BottlesPerCase = nw.GetBottlesPerCase(db);
I believe the model binder isn't picking up on your CaseProduction objects because they don't look like a CaseProduction objects.
You have renamed CaseCount, your CaseProductionSize has no Id (nor does you CaseProduction, and it's missing several properties. In your loop you have to include all properties, and keep the names consistent with the names of your POCOs. Otherwise the model binder won't know what they are. You can put all the properties in hidden fields if you want.
You must instantiate your nested Lists and complex models in your parent models constructor. The default model binder will not instantiate child classes.
If you do that, then you can use the EditorFor(m => m.Wine.CaseProductions) should work, and you don't need the complex view code you are using.
If you want to customize how the CaseProduction is rendered, then you can create a CaseProduction.cshtml file in ~/Shared/EditorTemplates and it will use this definition to render each item in the collection (it will automatically iterate over the collection for you).
Also, you shouldn't do linq queries in your view. Your problem there is that it looks like you're passing your data entity directly to the view. This is bad design. You need to instead create a ViewModel that contains only the information needed to render the view. Then, you filter your data before you assign it to the View model.

MVC Model Range Validator?

i wnat to validate the datetime, My Code is:
[Range(typeof(DateTime),
DateTime.Now.AddYears(-65).ToShortDateString(),
DateTime.Now.AddYears(-18).ToShortDateString(),
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime Birthday { get; set; }
but i get the error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
please help me?
This means the values for the Range attribute can't be determined at some later time, it has to be determined at compile time. DateTime.Now isn't a constant, it changes depending on when the code runs.
What you want is a custom DataAnnotation validator. Here's an example of how to build one:
How to create Custom Data Annotation Validators
Put your date validation logic in IsValid()
Here's an implementation. I also am using DateTime.Subtract() as opposed to negative years.
public class DateRangeAttribute : ValidationAttribute
{
public int FirstDateYears { get; set; }
public int SecondDateYears { get; set; }
public DateRangeAttribute()
{
FirstDateYears = 65;
SecondDateYears = 18;
}
public override bool IsValid(object value)
{
DateTime date = DateTime.Parse(value); // assuming it's in a parsable string format
if (date >= DateTime.Now.AddYears(-FirstDateYears)) && date <= DateTime.Now.AddYears(-SecondDateYears)))
{
return true;
}
return false;
}
}
Usage is:
[DateRange(ErrorMessage = "Must be between 18 and 65 years ago")]
public DateTime Birthday { get; set; }
It's also generic so you can specify new range values for the years.
[DateRange(FirstDateYears = 20, SecondDateYears = 10, ErrorMessage = "Must be between 10 and 20 years ago")]
public DateTime Birthday { get; set; }

Update one object with another in LINQ

I use LINQ to expose database objects to my application. Also in this application I get an object from my database called 'foo' like so.
public static Foo Get(int id)
{
MyDataContext db = new MyDataContext ();
Foo foo1 = (from f in db.Foos
where f.foo_id = id
select f).SingleOrDefault();
return foo1;
}
Now, if I change foo and want to update it without going through each field explicitly - I want to avoid this.
public static Foo Update(Foo foo)
{
MyDataContext db = new MyDataContext ();
Foo foo1 = (from f in db.Foos
where f.foo_id = foo.foo_id
select f).SingleOrDefault();
if(foo1 != null)
{
foo1.foo_nm = foo.foo_nm;
...
db.SubmitChanges();
}
}
Is there a way I can do something like in the entity framework:
foo1.UpdateVales(foo);
Create your own cloning method that copies all the properties you're interested in.
Alternatively you can use reflection to find all public properties and loop through them.
LINQ is no help here.
Here's a very simple (and also inefficient) extension function that can get you started (Make sure you import System.Reflection and System.Linq):
public static void GetDataFrom<T>(this T ObjToCopyTo, T ObjToCopyFrom) where T : class {
foreach (var property in ObjToCopyTo.GetType().GetProperties().Where(x => x.CanWrite))
property.SetValue(ObjToCopyTo, property.GetValue(ObjToCopyFrom, null), null);
}
You will probably find that you will have to put some special cases in for things like the primary keys, and may decide you do not want to copy every property exactly, but hopefully this shows the idea behind VVS's answer.
If you have linqpad installed you can play around with this idea with the following script:
void Main()
{
var foo = new Helper.Foo() {
field1 = "hi",
field2 = 5,
field3 = 3.14,
field4 = 'm'
};
var foo2 = new Helper.Foo();
foo.Dump("Foo Before");
foo2.Dump("Foo2 Before");
foo2.GetDataFrom(foo);
foo.Dump("Foo After");
foo2.Dump("Foo2 After");
}
public static class Helper {
public class Foo {
public string field1 { get; set; }
public int field2 { get; set; }
public double field3 { get; set; }
public char field4 { get; set; }
}
public static void GetDataFrom<T>(this T ObjToCopyTo, T ObjToCopyFrom) where T : class {
foreach (var property in ObjToCopyTo.GetType().GetProperties().Where(x => x.CanWrite))
property.SetValue(ObjToCopyTo, property.GetValue(ObjToCopyFrom, null), null);
}
}
I hope this helps!
I use automapper to avoid the heavy lifting to update the DB with MVC models, should work for mapping to the same object type and comes in handy for these sorts of coding styles.
After installing automapper ...
public ActionResult Example(TableModel model)
{
..
Mapper.CreateMap<TableModel, Table>();
Table myTable = new Table();
Mapper.Map(model, myTable);
...
}

Append OR subquery in Linq

I am trying to build a simple search against some entities (EF4, if that makes any difference). Passed into my search query is a list of criteria objects. The crieteria object looks like this:
public class ClaimSearchCirtieria
{
public Guid? FinancialYear { get; set; }
public bool AllClaimants { get; set; }
public IList<Guid> ClaimantIds { get; set; }
public bool AllExpenseCategories { get; set; }
public IList<ExpenseCategoryAndTypeCriteria> EpenseCategoryAndTypes { get; set; }
}
And the ExpenseCategoryAndTypeCriteria
public class ExpenseCategoryAndTypeCriteria
{
public Guid ExpenseCategory { get; set; }
public bool AllTypesInCatgeory { get; set; }
public IList<Guid> ExpenseTypes { get; set; }
}
Searching on financial years and claimants needs to be an AND query, then I need the expense categories and expense types to be appended as an OR sub query.
In essence I'm trying to do:
select *
from claims
where <financial year> AND <Claimants> AND (expense type 1 OR expense type 2 or expense category X)
So far I've got this:
public PagedSearchResult<Claim> Search(ClaimSearchCirtieria searchCriteria, int page, int pageSize)
{
var query = All();
if (searchCriteria.FinancialYear.HasValue)
{
query = from claim in query
where claim.FinancialYearId == searchCriteria.FinancialYear
select claim;
}
if (!searchCriteria.AllClaimants)
{
query = from claim in query
where searchCriteria.ClaimantIds.Contains(claim.ClaimantId)
select claim;
}
if (!searchCriteria.AllExpenseCategories)
{
foreach (var item in searchCriteria.EpenseCategoryAndTypes)
{
if (item.AllTypesInCatgeory)
{
//Just search on the category
query = query.Where(claim =>
(from transaction in claim.ClaimTransactions
where item.ExpenseCategory == transaction.ExpenseType.ExpenseCategoryId
select transaction).Count() > 0
);
}
else
{
//Search for the specified types
query = query.Where(claim =>
(from transaction in claim.ClaimTransactions
where item.ExpenseTypes.Contains(transaction.ExpenseTypeId)
select transaction).Count() > 0
);
}
}
}
return PagedSearchResult<Claim>.Build(query, pageSize, page);
}
What I'm currently seeing is that the last expense category requested is the only expense category I get results for. Also, looking at the code, it looks like I would expect this to be building a series of AND queries, rather that the required OR.
Any pointers?
You can do this with LINQKit's PredicateBuilder. You need to use AsExpandable() when composing Entity Framework queries.

Resources