How to resolve CA1502: Avoid excessive complexity? - c#

As per code analysis result, the following is the warning message,
CA1502 Avoid excessive complexity 'METHOD()' has a cyclomatic complexity of 27. Rewrite or refactor the method to reduce complexity to 25.
BusinessServices ReportService.cs 310
What is cyclomatic complexity and how to resolve this?

From Wikipedia:
The cyclomatic complexity of a section of source code is the count of the number of linearly independent paths through the source code. For instance, if the source code contained no decision points such as IF statements or FOR loops, the complexity would be 1, since there is only a single path through the code. If the code had a single IF statement containing a single condition, there would be two paths through the code: one path where the IF statement is evaluated as TRUE and one path where the IF statement is evaluated as FALSE.
The easiest way to resolve this is to break the method into two or more smaller methods. Visual Studio's built-in refactoring tools (such as Refactor -> Extract Method) can be used to extract a selected portion of code into another method.

CA1502 is a binary analyzer. In some cases your code isn't all that complex or can't be decomposed. Here is an example that resulted in a CA1502 warning:
public class Mapping : Profile
{
[SuppressMessage("Microsoft.Maintainability", "CA1502"]
public Mapping()
{
CreateMap<ItemInstance, ManifestRecords>()
.ForMember(dest => dest.SomeProperty1, opt => opt.MapFrom(src => src.SomeProperty1))
.ForMember(dest => dest.SomeProperty2, opt => opt.MapFrom(src => src.SomeProperty2))
.ForMember(dest => dest.SomeProperty3, opt => opt.MapFrom(src => src.SomeProperty3))
.ForMember(dest => dest.SomeProperty4, opt => opt.MapFrom(src => src.SomeProperty4))
.ForMember(dest => dest.SomeProperty5, opt => opt.MapFrom(src => src.SomeProperty5))
.ForMember(dest => dest.SomeProperty6, opt => opt.MapFrom(src => src.SomeProperty6))
.ForMember(dest => dest.SomeProperty7, opt => opt.MapFrom(src => src.SomeProperty7))
.ForMember(dest => dest.SomeProperty8, opt => opt.MapFrom(src => src.SomeProperty8))
.ForMember(dest => dest.SomeProperty9, opt => opt.MapFrom(src => src.SomeProperty9))
.ForMember(dest => dest.SomeProperty10, opt => opt.MapFrom(src => src.SomeProperty10))
.ForMember(dest => dest.SomeProperty11, opt => opt.MapFrom(src => src.SomeProperty11))
.ForMember(dest => dest.SomeProperty12, opt => opt.MapFrom(src => src.SomeProperty12))
.ForMember(dest => dest.SomeProperty13, opt => opt.MapFrom(src => src.SomeProperty13))
.ForMember(dest => dest.SomeProperty14, opt => opt.MapFrom(src => src.SomeProperty14))
.ForMember(dest => dest.SomeProperty15, opt => opt.MapFrom(src => src.SomeProperty15))
.ForMember(dest => dest.SomeProperty16, opt => opt.MapFrom(src => src.SomeProperty16));
}
}
You can see I'm using AutoMapper and there's not really any cyclomatic complexity in the source code, though there may be in the compiled binary representation.
You can suppress the warning with the attribute:
[SuppressMessage("Microsoft.Maintainability", "CA1502"]

Related

Automapper: Conditionally changing value of item in list

I have a list that I get from a database, I want to map that to another class and get the result. However, one of the items of that list (ContractTitle) has its own properties (ContractTitle.Buyer or ContractTitle.Seller which are strings). For the mapped result, I want to change the value of each ContractTitle to either ContractTitle.Buyer or ContractTitle.Seller.
The example below is what I am attempting to do, I don't really know how to proceed though. I feel I would need a custom resolver to actually achieve this. Sorry, I am very new to automapper, find the documentation a bit confusing. Thanks!
IEnumerable < Document > document; //=some data from db
var condition = true
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap < Document, Contract > ()
.ForMember(dest => dest.ContractTitle, opt => opt
.MapFrom((src, dest, destMember, context) => context
.Options.Items["ContractTitle"]))
.ForMember(dest => dest.ContractTemplateId, opt => opt
.MapFrom(x => x.DocumentTemplateId))
.ForMember(dest => dest.Id, opt => opt
.MapFrom(x => x.Id)));
configuration.AssertConfigurationIsValid();
IMapper mapper = configuration.CreateMapper();
if (condition) {
return mapper.Map < IEnumerable < Contract >> (document, opt => opt
.Items["ContractTitle"] = document.ContractTitle.Buyer);
} else {
return mapper.Map < IEnumerable < Contract >> (document, opt => opt
.Items["ContractTitle"] = document.ContractTitle.Seller);
}
Instead of passing the internal data that needs to be mapped into the map operation, it would probably be a better idea to pass in the condition itself, and let the mapper pick one or the other based on it while mapping each member individually:
IEnumerable < Document > document; //=some data from db
var condition = true
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<Document, Contract>()
.ForMember(dest => dest.ContractTitle, opt => opt
.MapFrom((src, dest, destMember, context) =>
((bool)context.Options.Items["Condition"])
? src.ContractTitle.Buyer
: src.ContractTitle.Seller))
.ForMember(dest => dest.ContractTemplateId, opt => opt
.MapFrom(x => x.DocumentTemplateId))
.ForMember(dest => dest.Id, opt => opt
.MapFrom(x => x.Id)));
configuration.AssertConfigurationIsValid();
IMapper mapper = configuration.CreateMapper();
return mapper.Map<IEnumerable<Contract>> (document, opt => opt
.Items["Condition"] = condition);

C# AutoMapper: set destination value by source value after validation in conditional mapping

I'm fairly new to AutoMapper and want to know how to set a destination member to a value based on a DIFFERENT source property value and if that value is null I just want to apply the default behaviour of Automapper (keep destination value when the source is null)
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt =>
opt.MapFrom(src => src.PropertyB!= null ? null : opt.UseDestinationValue())
)
This doesn't work (don't compile) the opt.UseDestinationValue() , what option can I use here?
Please help
Try setting a precondition for mapping destination property.
CreateMap<ClassA, ClassA>().ForMember(dest => dest.PropertyA, opt => opt.PreCondition((src, dest) => src.PropertyB != null));
This will map PropertyA only when PropertyB is not null. I did try a quick sample which gave the desired result.
I think you can use the PreCondition option For Mapping Property
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => src.PropertyB!= null);
opt.MapFrom(src => src.PropertyB);
});
Hope to help you
You can do as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => opt.Condition(src => (src.PropertyB!= null)));
});
Or as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => (src.PropertyB!=null));
opt.MapFrom(src => src.PropertyB); // mapping process takes place here
});
});
But the difference is that, the later runs sooner in the mapping process.
There is an excellent documentation in setting conditions for automapper:
https://docs.automapper.org/en/stable/Conditional-mapping.html

Automapper ProjectTo nested object

I'm trying to use the Queryable-Extensions ProjectTo method with Entity framework inside my WebApi. It works good for normal objects but when I try to use nested objects, Linq cannot translate the Map method for the nested object into a store expression.
var entity = db.tableX.where(x => x.ID == id).ProjectTo<MyDtoObject>().FirstOrDefault();
Automapping config:
CreateMap<EntityModel.tableX, MyDtoObject>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id))
.ForMember(dest => dest.TestObject, opts => opts.MapFrom(src => Mapper.Map<TestObject>(src)))
CreateMap<EntityModel.tableX, TestObject>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ForeignKey1.Id));
How can I use this ProjectTo() method with a nested object?
I know other solutions with a normal Select statement, or mapping from a query into memory list, will also work..

Automapper Member mapping on codition SeemsConvoluted

I'm trying to find the correct way to implement this.
return isOptionTrue? Mapper.Map<Context>(myObject) : Mapper.Map<ReplayContext>(myObject);
Context has all the fields I actually need.
public class ContextReplay: Context
{
//This class is a work around to trick Automapper
}
Then of course the mappings
Mapper.CreateMap<myObject, Context>()
.ForMember(x => x.Id, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => y.NormalProp));
Mapper.CreateMap<myObject, ContextReplay>()
.ForMember(x => x.WellId, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => y.ReplayProp));
As you can see, I really want:
Mapper.CreateMap<myObject, Context>()
.ForMember(x => x.WellId, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => isOptionTrue? y.NormProp: y.ReplayProp));
For questions: Yes i have an empty sublcass, becuase I cont have two unique MyObject to Context mappings. This whole thing seems like a super hacky work around. I can't imagine they really don't support something like this, but im at a loss. I have no idea how to get my bool passed along with it.
Perhaps my imagination is limited, but personally I find "auto mapping" to be of limited use, except when mapping between two "identical" classes. Wouldn't it be more straight-forward, faster, and easier to maintain the following bit of code?
var context = new Context {
WellId = myObject.Id,
Name = myObject.Name,
Important = (isOptionTrue ? myObject.NormProp : myObject.ReplayProp)
};

Using Automapper (.net c#) to map to a variable not in Src for use in linq2sql classes?

I have been using automapper pretty successfully lately but i have come across a small problem for mapping the Dest to a variable not a available in the Src.... An example explains it better.. basically i am mapping from dest to src as per instructions .. all working well but i need to now map a destination to a variable named reservationNumber which is local variable not part of ORDER ... anyone know how to do this??
I am using automapper to map from order to reservation for use in linq2sql as Reservation is my linq2sql class.
Is the small example, i would appreciate any input.
string reservationNumber = "1234567890"; // this is the local variable.. It will be dynamic in future..
Mapper.CreateMap<Order, Reservation>()
.ForMember(dest => dest.ReservationNumber, reservationNumber // THIS OBVIOUSLY FAILS)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name))
.ForMember(dest => dest.Surname1, opt => opt.MapFrom(src => src.surname1))
.ForMember(dest => dest.Surname2, opt => opt.MapFrom(src => src.surname2))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.email))
.ForMember(dest => dest.Telephone, opt => opt.MapFrom(src => src.telephone))
;
// Perform mapping
Reservation reservation = Mapper.Map<Order, Reservation>(order);
Try this:
Mapper.CreateMap<Order, Reservation>()
.ForMember(dest => dest.ReservationNumber, opt => opt.MapFrom(src => reservationNumber));
That MapFrom option takes any Func. Your other options would be to map to an existing destination object, with the reservation number already there. Or, use a custom value resolver (ResolveUsing), if you need to get the reservation number using a custom service or something.
The CreateMap call only needs to happen once per AppDomain, so you may want to check the other two options and see if they fit your needs.

Categories