Problem Setup
I'm attempting to implement validation rules into my app using the System.Linq.Dynamic.Core package, but I'm running into weird behavior. My idea is to have the ability to create and manage these validation rules from within the app itself. So, I have a ValidationRule object in my database defined as:
public class ValidationRule {
public string Description { get; set; }
public string ErrorMessage { get; set; }
public short Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
The NewExpression and OldExpression properties contain the expressions I want to pass to the dynamic version of Any(). The only important parts of the ValidationRule object are the Name, ErrorMessage, NewExpression, and OldExpression, so I project them into a DTO:
public class ValidationRuleDto {
public string ErrorMessage { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
This DTO is then passed to a ValidationRuleHandler to evaluate an old and a new instance of the object being validated with the expressions from the DTO. The ValidationRuleHandler looks like this:
public static class ValidationRuleHandler {
private static readonly ParsingConfig _parsingConfig = new() {
AreContextKeywordsEnabled = false
};
public static ICollection<string> Validate(
dynamic oldObject,
dynamic newObject,
IEnumerable<ValidationRuleDto> rules) => rules.Select(
_ => {
var oldResult = new[] {
oldObject
}.AsQueryable().Any(_parsingConfig, _.OldExpression);
var newResult = new[] {
newObject
}.AsQueryable().Any(_parsingConfig, _.NewExpression);
Debug.WriteLine($"Old => {oldObject.StageText} => {_.OldExpression} => {oldResult}");
Debug.WriteLine($"New => {newObject.StageText} => {_.NewExpression} => {newResult}");
return !(oldResult && newResult)
? null
: $"[{_.Name}] {_.ErrorMessage}";
}).Where(
_ => _ != null).ToHashSet();
}
For the old and new objects being passed to Validate() I project the object into an old snapshot (from the database as it is) and new snapshot (from the database but with the changes applied) DTOs. In the Validate() I add each of the objects to a single item collection and convert them to an IQueryable so I can use the dynamic Any(). My thought process here is that the expressions for the rule just need to evaluate to a true/false result, and since Any() returns true/false if the expression's conditions passed, it seemed the most appropriate way to go.
Problem
The problem that I'm running into is that the results I expect are not happening when running the app. For reference, the app is an ASP.NET MVC 5 (5.2.9) app targetting .NET Framework 4.8. However, when using LINQPad (5.46.00) to test the ValidationRuleHandler the results are correct. For example here's the output of the Debug statements from the app when it's processing three validation rules that apply to the user:
Old => Not Sold => (StageText != "Closed") => True
New => Closed => (StageText == "Closed") => False WRONG
Old => Not Sold => (StageText != "Not Sold") => True WRONG
New => Closed => (StageText == "Not Sold") and (ReasonNotSoldText == null) => False
Old => Not Sold => (StageText != "Ready to Invoice") => True
New => Closed => (StageText == "Ready to Invoice") and (PricingMethodText == null) => False
And here's the LINQPad result for the exact same validation rules and snapshot objects (exact same values):
Old => Not Sold => (StageText != "Closed") => True
New => Closed => (StageText == "Closed") => True
Old => Not Sold => (StageText != "Not Sold") => False
New => Closed => (StageText == "Not Sold") and (ReasonNotSoldText == null) => False
Old => Not Sold => (StageText != "Ready to Invoice") => True
New => Closed => (StageText == "Ready to Invoice") and (PricingMethodText == null) => False
As you can see, when ValidationRuleHandler is called from LINQPad it evaluates the expressions correctly, but when processed from the app, it's wrong.
I can't figure out why it's behaving this way. Thinking through it, I can't see a problem, and LINQPad behaves as expected, but the app is not and I don't know what else to do. If anyone has any suggestions, I'd appreciate them. I'm also open to alternative suggestions to accomplish the same goal if anyone has had to deal with something similar before.
When creating a .NET Full Framework 4.8 console app, with the following example code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
namespace ConsoleApp48DynamicLinq
{
public class ValidationRule
{
public string Description { get; set; }
public string ErrorMessage { get; set; }
public short Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
public class ValidationRuleDto
{
public string ErrorMessage { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
public class Test
{
public string StageText { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
var old = new Test
{
StageText = "Not Sold"
};
var #new = new Test
{
StageText = "Closed"
};
var validation = new ValidationRuleDto
{
OldExpression = "(StageText != \"Closed\")",
NewExpression = "(StageText == \"Closed\")"
};
var validations = new[] { validation };
ValidationRuleHandler.Validate(old, #new, validations);
}
public static class ValidationRuleHandler
{
private static readonly ParsingConfig ParsingConfig = new ParsingConfig
{
AreContextKeywordsEnabled = false
};
public static ICollection<string> Validate(
dynamic oldObject,
dynamic newObject,
IEnumerable<ValidationRuleDto> rules) => rules.Select(
_ =>
{
var oldResult = new[]
{
oldObject
}.AsQueryable().Any(ParsingConfig, _.OldExpression);
var newResult = new[]
{
newObject
}.AsQueryable().Any(ParsingConfig, _.NewExpression);
Console.WriteLine($"Old => {oldObject.StageText} => {_.OldExpression} => {oldResult}");
Console.WriteLine($"New => {newObject.StageText} => {_.NewExpression} => {newResult}");
return !(oldResult && newResult) ? null : $"[{_.Name}] {_.ErrorMessage}";
}).Where(_ => _ != null).ToHashSet();
}
}
}
It seems to work fine:
So, after many hours of trying different solutions to see where exactly it was failing, nothing worked, until I changed the expression to use Equals(). Instantly worked after that, and I'm not sure why this: (StageText.Equals("Closed")); works and this: (StageText == "Closed"); doesn't.
I'm still baffled why the original way worked when I tested it in LINQPad but not in the app. #Stef also confirmed it works, so I'm confused. Maybe it has something to do with the snapshot values being pulled from the database, or the validation rule expressions also being pulled from the database?
Anyway, it works with Equals() so I'm rolling with it.
Related
Say I have a class, I want to select multiple objects of it but create one unified object in the end. This is because of the requirement for the collection properties of the object to be combined.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Nozomi.Base.Core;
namespace Nozomi.Data.Models.Currency
{
public class Currency : BaseEntityModel
{
public Currency(ICollection<Currency> currencies)
{
if (currencies.Any())
{
var firstCurr = currencies.FirstOrDefault();
if (firstCurr != null)
{
// Doesn't matter...
Id = firstCurr.Id;
CurrencyTypeId = firstCurr.Id;
CurrencyType = firstCurr.CurrencyType;
Abbrv = firstCurr.Abbrv;
Name = firstCurr.Name;
CurrencySourceId = firstCurr.CurrencySourceId;
CurrencySource = firstCurr.CurrencySource;
WalletTypeId = firstCurr.WalletTypeId;
PartialCurrencyPairs = currencies
.SelectMany(c => c.PartialCurrencyPairs)
.DefaultIfEmpty()
.ToList();
}
}
}
[Key]
public long Id { get; set; }
public long CurrencyTypeId { get; set; }
public CurrencyType CurrencyType { get; set; }
public string Abbrv { get; set; } // USD? MYR? IND?
public string Name { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// This will have a number if it is a crypto pair to peg to proper entities
public long WalletTypeId { get; set; } = 0;
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
return !String.IsNullOrEmpty(Abbrv) && !String.IsNullOrEmpty(Name) && CurrencyTypeId > 0 && CurrencySourceId > 0;
}
}
}
Here's what a PartialCurrencyPair is:
namespace Nozomi.Data.Models.Currency
{
/// <summary>
/// Partial currency pair.
/// </summary>
public class PartialCurrencyPair
{
public long CurrencyId { get; set; }
public long CurrencyPairId { get; set; }
public bool IsMain { get; set; } = false;
public CurrencyPair CurrencyPair { get; set; }
public Currency Currency { get; set; }
}
}
So basically, if you want to make EURUSD, you'll have to take two currencies to form a pair. A CurrencyPair is made up of two PartialCurrencyPairs. The reason why we can have many EUR or many USDs is that they come from different sources.
Here's what a CurrencyPair is:
public class CurrencyPair : BaseEntityModel
{
[Key]
public long Id { get; set; }
public CurrencyPairType CurrencyPairType { get; set; }
/// <summary>
/// Which CPC to rely on by default?
/// </summary>
public string DefaultComponent { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// =========== RELATIONS ============ //
public ICollection<CurrencyPairRequest> CurrencyPairRequests { get; set; }
public ICollection<WebsocketRequest> WebsocketRequests { get; set; }
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
var firstPair = PartialCurrencyPairs.First();
var lastPair = PartialCurrencyPairs.Last();
return (CurrencyPairType > 0) && (!string.IsNullOrEmpty(APIUrl))
&& (!string.IsNullOrEmpty(DefaultComponent))
&& (CurrencySourceId > 0)
&& (PartialCurrencyPairs.Count == 2)
&& (firstPair.CurrencyId != lastPair.CurrencyId)
&& (!firstPair.IsMain == lastPair.IsMain);
}
}
I have an IQueryable to combine into one single currency.
Code with comments (The comments basically tells you what I'm trying to achieve.
var query = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
// Something here that will join the PartialCurrencyPair collection together and create one single Currency object.
.SingleOrDefault();
How do I come about it? Thank you so much in forward! Here's the
progress I've made so far and it works, but I'm pretty LINQ has a beautiful way to make this better and optimised:
var combinedCurrency = new Currency(_unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Include(c => c.PartialCurrencyPairs)
.ThenInclude(pcp => pcp.CurrencyPair)
.ThenInclude(cp => cp.CurrencyPairRequests)
.ThenInclude(cpr => cpr.RequestComponents)
.ThenInclude(rc => rc.RequestComponentDatum)
.ThenInclude(rcd => rcd.RcdHistoricItems)
.ToList());
return new DetailedCurrencyResponse
{
Name = combinedCurrency.Name,
Abbreviation = combinedCurrency.Abbrv,
LastUpdated = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.OrderByDescending(rc => rc.ModifiedAt)
.FirstOrDefault()?
.ModifiedAt ?? DateTime.MinValue,
WeeklyAvgPrice = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc =>
rc.ComponentType.Equals(ComponentType.Ask) ||
rc.ComponentType.Equals(ComponentType.Bid)))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromDays(7))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Average(),
DailyVolume = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc => rc.ComponentType.Equals(ComponentType.VOLUME)
&& rc.DeletedAt == null && rc.IsEnabled))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromHours(24))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Sum(),
Historical = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.Where(rc => componentTypes != null
&& componentTypes.Any()
&& componentTypes.Contains(rc.ComponentType)
&& rc.RequestComponentDatum != null
&& rc.RequestComponentDatum.IsEnabled
&& rc.RequestComponentDatum.DeletedAt == null
&& rc.RequestComponentDatum.RcdHistoricItems
.Any(rcdhi => rcdhi.DeletedAt == null &&
rcdhi.IsEnabled))
.ToDictionary(rc => rc.ComponentType,
rc => rc.RequestComponentDatum
.RcdHistoricItems
.Select(rcdhi => new ComponentHistoricalDatum
{
CreatedAt = rcdhi.CreatedAt,
Value = rcdhi.Value
})
.ToList())
};
Here's the end result I want on that single object: A DetailedCurrencyResponse object.
public class DistinctiveCurrencyResponse
{
public string Name { get; set; }
public string Abbreviation { get; set; }
public DateTime LastUpdated { get; set; }
public decimal WeeklyAvgPrice { get; set; }
public decimal DailyVolume { get; set; }
}
A historical datum is basically a kvp, where the Key (ComponentType) is an enum.
public class DetailedCurrencyResponse : DistinctiveCurrencyResponse
{
public Dictionary<ComponentType, List<ComponentHistoricalDatum>> Historical { get; set; }
}
public class ComponentHistoricalDatum
{
public DateTime CreatedAt { get; set; }
public string Value { get; set; }
}
The query you have outlined will attempt to return you a single Currency object, but given you are looking for any with a given abbreviation, if multiple currency objects share an abbreviation, the SingleOrDefault could error due to multiple returns.
It sounds like you want to define a structure to represent the currency pairs. That structure is not a Currency entity, but a different data representation. These are commonly referred to as ViewModels or DTOs. Once you've defined what you want to return, you can use .Select() to populate that from the Currency and applicable abbreviations.
For instance, if I create a CurrencySummaryDto which will have the currency ID, Abbrevation, and a string containing all of the applicable pairs:
public class CurrencySummaryDto
{
public long CurrencyId { get; set; }
public string Abbreviation { get; set; }
public string Pairs { get; set;}
}
... then the query...
var currencySummary = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
.AsNoTracking()
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Select( c => new {
c.Id,
c.Abbrv,
Pairs = c.PartialCurrencyPairs.Select(pc => pc.PairName).ToList() // Get names of pairs, or select another annonymous type for multiple properties you care about...
}).ToList() // Alternatively, when intending for returning lots of data use Skip/Take for paginating or limiting resulting data.
.Select( c => new CurrencySummaryDto
{
CurrencyId = c.Id,
Abbreviation = c.Abbrv,
Pairs = string.Join(", ", c.Pairs)
}).SingleOrDefault();
This is if you want to do something like combine data from the currency pairs into something like a string. If you're happy to leave them as a collection of simplified data, then the extra anonymous type and .ToList() are not required, just select directly into the Dto structure. This example combines the data into a string where string.Join() is not supported in EF expressions so we have to get our data out into objects to hand over to Linq2Object for the final mapping.
Edit: Ok, you're requirement/example just got a whole lot more complicated with the pair structure, but you should be able to leverage this into the query rather than selecting the entire graph of entities by moving the selection of those values up into the main query... However...
Given the complexity of the data relationships, the approach I would recommend using since this would be assumed to be a read-only result, would be to construct a View in the database to flatten these averages and totals, then bind a simplified entity to this view rather than attempting to manage this with EF Linq. I believe it can be done with linq, but it will be quite onerous to look at, and a view-based summary entity would be a lot cleaner while keeping the execution of this logic to be executed in the database.
I have a list of Users whom are attached to applications that included clients. I'm looking to filter a list of users by the application and client via Linq and am spinning.
Ideally I'd be using a single statement where Application.Name == "example" that are also in ClientApp.Id == 1.
This is where I'm at thus far but am having some internal brain issues regarding nesting. Any help is appreciated
var users2 = users.Where(x => x.App.Select(y => y.Name).Contains("example"));
public class User
{
public string FirstName { get; set; }
public List<Application> App { get; set; }
}
public class Application
{
public string Name { get; set; }
public List<ClientApp> Client { get; set; }
}
public class ClientApp
{
public string Id { get; set; }
}
You can use nested calls to Enumerable.Any to filter this:
var filtered = users.Where(u =>
u.App.Any(
a => a.Name == "example"
&& a.Client.Any(c => c.Id == 1)));
I am using AutoMapper to map DTOs to entities. Also, my WCF services are being consumed by SAP.
The issue is that SAP sends me empty strings instead of nulls (that is, "" instead of null).
So I basically need to go through every field of the DTO I am receiving, and replace empty strings by nulls. Is there an easy way to accomplish this with AutoMapper?
Consider value transform construction for mapper profile
CreateMap<Source, Destination>()
.AddTransform<string>(s => string.IsNullOrEmpty(s) ? null : s);
This construction will transform all 'string' type members and if they null or empty replace with null
Depends on what you are after - if there are string fields for which you would like to preserve the empty string and not convert to null, or you want to threat all of them the same. The provided solution is if you need to threat them all the same. If you want to specify individual properties for which the empty to null conversion should happen, use ForMemeber() instead of ForAllMembers.
Convert all solution:
namespace Stackoverflow
{
using AutoMapper;
using SharpTestsEx;
using NUnit.Framework;
[TestFixture]
public class MapperTest
{
public class Dto
{
public int Int { get; set; }
public string StrEmpty { get; set; }
public string StrNull { get; set; }
public string StrAny { get; set; }
}
public class Model
{
public int Int { get; set; }
public string StrEmpty { get; set; }
public string StrNull { get; set; }
public string StrAny { get; set; }
}
[Test]
public void MapWithNulls()
{
var dto = new Dto
{
Int = 100,
StrNull = null,
StrEmpty = string.Empty,
StrAny = "any"
};
Mapper.CreateMap<Dto, Model>()
.ForAllMembers(m => m.Condition(ctx =>
ctx.SourceType != typeof (string)
|| ctx.SourceValue != string.Empty));
var model = Mapper.Map<Dto, Model>(dto);
model.Satisfy(m =>
m.Int == dto.Int
&& m.StrNull == null
&& m.StrEmpty == null
&& m.StrAny == dto.StrAny);
}
}
}
You can just define string mapping like this:
cfg.CreateMap<string, string>()
.ConvertUsing(s => string.IsNullOrWhiteSpace(s) ? null : s);
You can be specific with the properties too.
cfg.CreateMap<Source, Dest>()()
.ForMember(destination => destination.Value, opt => opt.NullSubstitute(string.Empty)));
Remember if you are using ReverseMap() place it in the very last something like following,
cfg.CreateMap<Source, Dest>()()
.ForMember(destination => destination.Value, opt => opt.NullSubstitute(string.Empty)))
.ReverseMap();;
I use c# 4 asp.net and EF 4. I'm precompiling a query, the result should be a collection of Anonymous Type.
At the moment I use this code.
public static readonly Func<CmsConnectionStringEntityDataModel, string, dynamic>
queryContentsList =
CompiledQuery.Compile<CmsConnectionStringEntityDataModel, string, dynamic>
(
(ctx, TypeContent) => ctx.CmsContents.Where(c => c.TypeContent == TypeContent
& c.IsPublished == true & c.IsDeleted == false)
.Select(cnt => new
{
cnt.Title,
cnt.TitleUrl,
cnt.ContentId,
cnt.TypeContent, cnt.Summary
}
)
.OrderByDescending(c => c.ContentId));
I suspect the RETURN for the FUNCTION Dynamic does not work properly and I get this error
Sequence contains more than one element enter code here.
I suppose I need to return for my function a Collection of Anonymous Types...
Do you have any idea how to do it? What I'm doing wrong? Please post a sample of code thanks!
Update:
public class ConcTypeContents
{
public string Title { get; set; }
public string TitleUrl { get; set; }
public int ContentId { get; set; }
public string TypeContent { get; set; }
public string Summary { get; set; }
}
public static readonly Func<CmsConnectionStringEntityDataModel, string, ConcTypeContents> queryContentsList =
CompiledQuery.Compile<CmsConnectionStringEntityDataModel, string, ConcTypeContents>(
(ctx, TypeContent) => ctx.CmsContents.Where(c => c.TypeContent == TypeContent & c.IsPublished == true & c.IsDeleted == false)
.Select(cnt => new ConcTypeContents { cnt.Title, cnt.TitleUrl, cnt.ContentId, cnt.TypeContent, cnt.Summary }).OrderByDescending(c => c.ContentId));
You should not return an anonymous type from a method. Create a concrete type to hold whatever data is currently held in the anonymous type and return that instead.
...
.Select(cnt =>
new ConcType{
Title = cnt.Title,
TitleUrl = cnt.TitleUrl,
ContentId = cnt.ContentId,
TypeContent = cnt.TypeContent,
Summary = cnt.Summary })
...
where:
class ConcType
{
public string Title {get; set;}
//etc...
}
Hi I have some major problems with auto mapper and it being slow. I am not sure how to speed it up.
I am using nhibernate,fluent nhibernate and asp.net mvc 3.0
[Serializable()]
public class Test
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual DateTimeDate { get; set; }
public virtual IList<Reminder> Reminders { get; set; }
public virtual IList<Reminder2> Reminders2 { get; set; }
public virtual Test2 Test2 { get; set; }
public Test()
{
Reminders = new List<Reminders>();
Reminders2 = new List<Reminders2>();
}
}
So as you can see I got some properties, Some other classes as in my database I have references between them.
I then do this
var a = // get all items (returns a collection of Test2)
var List<MyViewModel> collection = new List<MyViewModel>();
foreach (Test2 t in a)
{
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
// view model
public class MyViewModel
{
public int Id { get; private set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateTimeDate { get; set; }
public string FormatedDueDate { get; set; }
public string Test2Prefix { get; set; }
public string Test2BackgroundColor { get; set; }
public string SelectedDateFilter { get; set; }
public bool DescState { get; set; }
public bool AlertState { get; set; }
/// <summary>
/// Constructor
/// </summary>
public MyViewModel()
{
// Default values
SelectedDateFilter = "All";
DescState = false;
AlertState = false;
}
/// <summary>
/// Sets the date formatter string used
/// </summary>
/// <param name="dateFormat"></param>
public void SetDateFormat(DateTime dueDate, string dateFilter)
{
// simple if statement to format date.
}
}
// mapping
Mapper.CreateMap<Test2,MyViewModel>().ForMember(dest => dest.DescState, opt =>
opt.ResolveUsing<DescStateResolver>())
.ForMember(dest => dest.AlertState, opt =>
opt.ResolveUsing<AlertStateResolver>());
// resolvers
public class AlertStateResolver : ValueResolver<Task, bool>
{
protected override bool ResolveCore(Task source)
{
if (source.Reminders.Count > 0 || source.Reminders2.Count > 0)
{
return true;
}
else
{
return false;
}
}
}
public class DescStateResolver : ValueResolver<Task,bool>
{
protected override bool ResolveCore(Task source)
{
if (String.IsNullOrEmpty(source.Description))
{
return false;
}
else
{
return true;
}
}
}
Ignore the weird names and any typos my real object works just fine and makes sense.
So I used the stop watch and did this
Stopwatch a = new Stopwatch()
foreach (Test2 t in a)
{
a.Start()
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
a.Stop()
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
var b = a.Elapsed; // comes back with 32 seconds.
I need to optimized this very badly.
Instead of:
var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = new List<MyViewModel>();
foreach (Test2 t in a)
{
MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
collection.Add(vm);
}
Try:
var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = Mapper.Map<IEnumerable<Test2>, IEnumerable<MyViewModel>>(a);
which is equivalent to the first except the SetDateFormat call which you could do at your mapping definition. It might also be faster.
If you have a mapping defined between Test2 => MyViewModel AutoMapper automatically provides one for IEnumerable<Test2> => IEnumerable<MyViewModel> so that you don't need to loop.
Also you have mentioned NHibernate in your question. Make sure that your source object along with its collections is eagerly loaded from the database before passing it to the mapping layer or you cannot blame AutoMapper for being slow because when it tries to map one of the collections of your source object it hits the database because NHibernate didn't fetch this collection.
Another thing to look for is mapping code that throws exceptions. AutoMapper will catch these silently, but catching exceptions in this way impacts performance.
So if SomethingThatMightBeNull is often null, then this mapping will perform poorly due to the NullreferenceExceptions :
.ForMember(dest => dest.Blah, c.MapFrom(src=>src.SomethingThatMightBeNull.SomeProperty))
I've found making a change like this will more than half the time the mapping takes:
.ForMember(dest => dest.Blah, c.MapFrom(src=> (src.SomethingThatMightBeNull == null
? null : src.SomethingThatMightBeNull.SomeProperty)))
Update: C# 6 syntax
.ForMember(dest => dest.Blah, c.MapFrom(src => (src.SomethingThatMightBeNull?.SomeProperty)))
Was Able to improve Launch Time when added this
.ForAllMembers(options => options.Condition(prop => prop.SourceValue != null));
at end of Each
.CreateMap<..,..>()
If your subcollections are large you might benefit from using "Any()" instead of the "Count > 1". The Any function will only have to iterate once while the Count might need to iterate the entmes collection (depending on the implementation).
Not sure if this is causing any issues in your case, but beware of serializing auto-implemented properties.
Each time your code is compiled, the name of each (anonymous) backing field is picked at random by the compiler. So you may see some surprising exceptions if you serialize data with a progran that is compiled at one time and de-serialize it with a different program.
I fixed the same issue as yours. It also costs me 32s for mapping only one object.
So, I use opts.Ignore() to deal with some customized object as below:
CreateMap<SiteConfiguration, Site>()
.ForMember(x => x.SubSystems, opts => opts.Ignore())
.ForMember(x => x.PointInformations, opts => opts.Ignore())
.ForMember(x => x.Schedules, opts => opts.Ignore())
.ForMember(x => x.EquipmentDefinitions, opts => opts.Ignore());
After that, it just cost a few milliseconds.
A good tip is to optimize the configuration of AutoMapper, use Ignore for the properties of the ViewModels, and make the method call to validate the mappings "Mapper.AssertConfigurationIsValid()".
Mapper.Initialize(cfg =>
{
cfg.ValidateInlineMaps = true;
cfg.AllowNullCollections = false;
cfg.AllowNullDestinationValues = true;
cfg.DisableConstructorMapping(); // <= In the case of my project, I do not use builders, I had a performance gain.
cfg.AddProfile<DomainToViewModelMappingProfile>();
cfg.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();