I am having some difficulty projecting my POCO object to a DTO object
My POCO object
public class LogEntry
{
public LogEntry()
{
this.Username = Environment.UserName;
this.LogNote = String.Empty;
var now = DateTime.Now;
this.LoginTime = now;
this.LastSeenInput = now;
this.ServerName = Environment.MachineName;
}
public int LogEntryId { get; set; }
[Required(AllowEmptyStrings = false)]
[Column(TypeName = "nvarchar")]
[MaxLength(64)]
public string Username { get; set; }
[Required(AllowEmptyStrings = true)]
[Column(TypeName = "varchar")]
[MaxLength(20)]
public string ServerName { get; set; }
public DateTime LoginTime { get; set; }
public TimeSpan ConnectedFor { get; set; }
public TimeSpan IdleFor { get; set; }
public DateTime? LastSeenInput { get; set; }
[Required(AllowEmptyStrings = true)]
public string LogNote { get; set; }
}
My DTO object
public sealed class ClientEventDTO
{
public int LogEntryId { get; set; }
public string Username { get; set; }
public string ServerName { get; set; }
public DateTime LoginTime { get; set; }
public TimeSpan ConnectedFor { get; set; }
public string LogNote { get; set; }
public TimeSpan IdleForWithLast { get; set; }
}
What I am trying to do is I want IdleForWithLast to represent
public TimeSpan IdleForWithLast
{
get
{
var tmp = IdleFor;
if (LastSeenInput.HasValue)
tmp = tmp.Add(DateTime.Now - LastSeenInput.Value);
return tmp;
}
}
However when I do this
static void Main(string[] args)
{
Mapper.CreateMap<LogEntry, ClientEventDTO>()
.ForMember(dto => dto.IdleFor,
a => a.MapFrom(ent => ent.LastSeenInput == null ?
ent.IdleFor :
ent.IdleFor + (DateTime.Now - ent.LastSeenInput.Value)
)
);
Mapper.AssertConfigurationIsValid();
using (var ctx = new LoginHistoryContext())
{
var username = "Scott";
var query = ctx.LogEntries.Where(entry => entry.Username == username).Project().To<ClientEventDTO>();
var result = query.ToList();
Debugger.Break();
}
}
I get the following exception
System.ArgumentException was unhandled
HResult=-2147024809
Message=DbArithmeticExpression arguments must have a numeric common type.
Source=System.Data.Entity
Changing ent.IdleFor + (DateTime.Now - ent.LastSeenInput.Value) to ent.IdleFor.Add(DateTime.Now - ent.LastSeenInput.Value) just gives me a different exception
System.NotSupportedException was unhandled
HResult=-2146233067
Message=LINQ to Entities does not recognize the method 'System.TimeSpan Add(System.TimeSpan)' method, and this method cannot be translated into a store expression.
Source=System.Data.Entity
What is the correct way to do this kind of operation using while maintaining compatibility QueryableExtensions?
If you don't care about compatibility it can be easily done this way
static void Main(string[] args)
{
Mapper.CreateMap<LogEntry, ClientEventDTO>()
.ForMember(dto => dto.IdleFor, opt=>opt.ResolveUsing<CustomResolver>());
Mapper.AssertConfigurationIsValid();
using (var ctx = new LoginHistoryContext())
{
var username = "Scott";
var query = ctx.LogEntries.Where(entry => entry.Username == username);
var result = Mapper.Map<List<ClientEventDTO>>(query.ToList());
Debugger.Break();
}
}
public class CustomResolver : ValueResolver<LogEntry, TimeSpan>
{
protected override TimeSpan ResolveCore(LogEntry source)
{
var tmp = source.IdleFor;
if (source.LastSeenInput.HasValue)
tmp = tmp.Add(DateTime.Now - source.LastSeenInput.Value);
return tmp;
}
}
and that is the work around I will likely use for this situation, but I would like to know how to handle this in the future in case I am in a situation where I must use QueryableExtensions and I need to do something similar.
You can ignore this member, and map it manually after mapping:
Mapper.CreateMap<LogEntry, ClientEventDTO>()
.ForMember(dto => dto.IdleForWithLast, m => m.Ignore())
.AfterMap((ent, dto) =>
dto.IdleForWithLast = ent.LastSeenInput.HasValue ?
ent.IdleFor + (DateTime.Now - ent.LastSeenInput.Value) : ent.IdleFor);
Or change default mapping in after map:
Mapper.CreateMap<LogEntry, ClientEventDTO>()
.ForMember(dto => dto.IdleForWithLast, m => m.MapFrom(ent => ent.IdleFor))
.AfterMap((ent, dto) => {
if (ent.LastSeenInput.HasValue)
dto.IdleForWithLast += DateTime.Now - ent.LastSeenInput.Value;
});
Related
Please help! I am getting error in the line :
details.NominalVoltage = String.Join(",", paneldetails?.NominalVoltage?.ToArray());
I have below code in my builder.
foreach (var panel in panelAddresses.Take(2))
{
var paneldetails = new SM_NFPA72ReportPage1();
details.batteryDetails = new List<Battery>();
var AssociatedPrimaryPowers = new Repository(new BuildConnection()).GetPanelPrimarypowerDevcies(reportInput.UseroId, panel, reportInput.BuildingId, reportInput.TestSessionId[0]).Result;
AssociatedPrimaryPowers.ForEach(x => paneldetails?.batteryDetails?.Add(new Battery
{
NominalVoltage = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "nominalVoltage")?.FirstOrDefault()?.Value,
NominalAmps = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "nominalAmps")?.FirstOrDefault()?.Value,
NominalLocation = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "disconnectLocation")?.FirstOrDefault()?.Value,
Protection = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "overCurrentType")?.FirstOrDefault()?.Value,
ProtectionAmps = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "overCurrentAmps")?.FirstOrDefault()?.Value,
ProtectionLocation = deviceDetailsList?.CustomProperty?.Where(y => y.fieldName == "powerLocation")?.FirstOrDefault()?.Value,
}));
details.NominalVoltage = String.Join(",", paneldetails?.NominalVoltage?.ToArray());
details.NominalAmps = String.Join(",", paneldetails?.NominalAmps?.ToArray());
details.NominalLocation = String.Join(",", paneldetails?.NominalLocation?.ToArray());
details.Protection = String.Join(",", paneldetails?.Protection?.ToArray());
details.ProtectionAmps = String.Join(",", paneldetails?.ProtectionAmps?.ToArray());
details.ProtectionLocation = String.Join(",", paneldetails?.ProtectionLocation?.ToArray());
}
Below attached is my model for above builder:
public class SM_NFPA72ReportPage1 : IReportModel
{
public string NominalVoltage { get; set; }
public string NominalAmps { get; set; }
public string NominalLocation { get; set; }
public string Protection { get; set; }
public string ProtectionAmps { get; set; }
public string ProtectionLocation { get; set; }
public List<Battery> batteryDetails { get; set; }
public List<PanelDetailsInfo> panelInfo { get; set; }
}
I am reusing the Battery model to fetch the values from repository
public class Battery
{
public string NominalVoltage { get; set; }
public string NominalAmps { get; set; }
public string NominalLocation { get; set; }
public string Protection { get; set; }
public string ProtectionAmps { get; set; }
public string ProtectionLocation { get; set; }
}
The exception tells you that the parameter value is null, that should mean that:
paneldetails?.NominalVoltage?.ToArray()
...gives you a null result, and that the string.Join method does not accept it.
You need to make sure that you do not provide a null value to the method.
This can be achieved in multiple ways, for example by checking for null value before calling the method:
if (panelDetails?.NominalVoltage != null)
{
details.NominalVoltage = String.Join(",", paneldetails.NominalVoltage.ToArray());
}
or by returning a empty array by default if it is null:
details.NominalVoltage = String.Join(",", paneldetails?.NominalVoltage?.ToArray() ?? Array.Empty<string>());
This question already has answers here:
Recursively Get Properties & Child Properties Of A Class
(5 answers)
Closed 2 years ago.
I am trying to write an universal search to use for all objects.
I have this code, which is working fine to search in just one object's properties, but I would also like to search also in properties in related objects.
Eg. I have these Models/Objects
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address{ get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
public int Id { get; set; }
public DateTime From{ get; set; }
public DateTime To{ get; set; }
public string Comment{ get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
and I want to search if any of properties contains some a string eg. "Peter", I will call it this way:
string searchString = "Peter";
var customers = db.Customers
.Include(x => x.Contracts)
.WhereAnyPropertiesOfSimilarTypeContains(searchString);
this code will check if any properties of 'Customer' contains string "Peter".
But I would also need to check if the related model 'Contract' contains "Peter.
public static class EntityHelper
{
public static IQueryable<TEntity> WhereAnyPropertiesOfSimilarTypeContains<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
{
var param = Expression.Parameter(typeof(TEntity));
var predicate = PredicateBuilder.False<TEntity>(); //--- True to equal
var entityFields = GetEntityFieldsToCompareTo<TEntity, TProperty>();
foreach (var fieldName in entityFields)
{
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
Expression.PropertyOrField(param, fieldName), method,
Expression.Constant(value)), param);
predicate = predicate.Or(predicateToAdd); //--- And to equal
}
return query.Where(predicate);
}
// TODO: You'll need to find out what fields are actually ones you would want to compare on.
// This might involve stripping out properties marked with [NotMapped] attributes, for
// for example.
public static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
{
Type entityType = typeof(TEntity);
Type propertyType = typeof(TProperty);
var fields = entityType.GetFields()
.Where(f => f.FieldType == propertyType)
.Select(f => f.Name);
var properties = entityType.GetProperties()
.Where(p => p.PropertyType == propertyType)
.Select(p => p.Name);
return fields.Concat(properties);
}
}
Thanks.
After reread the question. I don't know what are you trying, but here I put the idea I have what are you looking for.
public class Customer : AbstractEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract : AbstractEntity
{
//what property here can be string "Peter"? Comments?
//what are you trying?
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Comment { get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
public abstract class AbstractEntity
{
//this method can be used to preselect properties you want
protected virtual Tuple<bool, ICollection<PropertyInfo>> PropertyCollector()
{
return new Tuple<bool, ICollection<PropertyInfo>>(false, null);
}
public IEnumerable<Tuple<Type, object>> GetRowValues()
{
foreach (var prop in GetRows())
{
yield return new Tuple<Type, object>(prop.PropertyType, prop.GetValue(this));
}
}
public ICollection<PropertyInfo> GetRows()
{
var tuple = PropertyCollector();
ISet<PropertyInfo> pInfo;
if (tuple.Item1)
{
pInfo = new HashSet<PropertyInfo>(tuple.Item2);
}
else //search all non virtual, private, protected properties, "following POCO scheme"
{
pInfo = new HashSet<PropertyInfo>();
foreach (var prop in GetType().GetProperties())
{
foreach (var access in prop.GetAccessors())
{
if ((!access.IsVirtual && !access.IsPrivate) && (prop.CanWrite && prop.CanRead))
{
pInfo.Add(prop);
}
}
}
}
return pInfo;
}
}
public static class Searchs
{
public static ICollection<object> ObjectsWithStringFound(ICollection<Customer> customers, string toBeFound)
{
var objs = new List<object>();
foreach (var cust in customers)
{
var strings = cust.GetRowValues().Where(tpl => tpl.Item1 == typeof(string)).Select(tpl => tpl.Item2);
var contracts = cust.GetRowValues().Where(tpl => tpl.Item2 is IEnumerable<Contract>).Select(tpl => tpl.Item2);
if (strings.Any(str => str == toBeFound))
{
objs.Add(cust);
}
else if (contracts.Any(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound))
{ //What I suppose I must "match" with "Peter"??!?!
objs.Add(contracts.First(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound));
}
}
return objs;
}
}
I think we aren't understanding each other.
Error occurred when trying to add qresult. Anyone can please advise / guide how to fix / amend my code? thanks
cannot convert from System.Collections.Generic.List<> to System.Collections.Generic.IEnumerable<>
public class SystemAccessList
{
public SystemAccess SystemAccess { get; set; }
public List<SystemAccess> SystemAccessList { get; set; }
SystemDbContext db;
public void setDbContext(PALMSConfigDbContext _db)
{
db = _db;
}
public SystemAccessList GetAccessList(SystemAccessList systemAccessList)
{
var qresult = db.tbl_SystemAccessList
.GroupBy(g => g.ClassID)
.AsEnumerable().Select(c =>
{
var rr = new ResultRow { Class = c.Key };
foreach (var r in db.tbl_SystemAccessList.GroupBy(gg => gg.StudentID))
{
rr.Student[r.Key] = "N";
}
foreach (var r in c.Where(w => w.ClassID == c.Key))
{
rr.Student[r.StudentID] = "Y";
}
return rr;
}).ToList();
systemAccessList.SystemAccessList.AddRange(qresult);
return systemAccessList;
}
class ResultRow
{
public ResultRow()
{
Student = new Dictionary<string, string>();
}
public string Class { get; set; }
public IDictionary<string, string> Student { get; set; }
public string GetHeader()
{
return Student.Aggregate("Class", (current, s) => current + "|" + s.Key);
}
public string GetSolidRow()
{
return Student.Aggregate(Class, (current, s) => current + "|" + s.Value);
}
}
public class SystemAccess
{
[Key]
public int ID { get; set; }
public string ClassID { get; set; }
public string StudentID { get; set; }
}
Your qresult will be of type List<ResultRow> because of the Select you have there. A List<ResultRow> is also an IEnumerable<ResultRow> which (when I assume ResultRow is a reference type (class)) by turn is an IEnumerable<Xxx> for any Xxx that is either a base class of ResultRow, or an interface that ResultRow implements.
Your SystemAccessList is of type List<SystemAccess> whose AddRange method than requires an IEnumerable<SystemAccess>.
So all would be fine if a ResultRow were a SystemAccess (by inheritance). But it is not. So you should get a message at compile-time saying the List<ResultRow> is not an IEnumerable<SystemAccess>.
My object mapping requires me to pass the source object and an additional object to be able to map the destination object appropriately. However, I am unable to determine a way to be able to get that done.
public class SourceDto
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string SourceValue3 { get; set; }
public string SourceValue4 { get; set; }
}
public class AnotherSourceDto
{
public string AnotherSourceValue1 { get; set; }
public string AnotherSourceValue2 { get; set; }
public string AnotherSourceValue3 { get; set; }
public string AnotherSourceValue4 { get; set; }
}
public class Destination
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string DestValue3 { get; set; }
}
public string ConvertToDestValue3(string sourceValue3, string anotherSourceValue2)
{
// Some logic goes here
return sourceValue3 + " " + anotherSourceValue2;
}
void Main()
{
var config = new MapperConfiguration(
cfg =>cfg.CreateMap<SourceDto, Destination>()
.ForMember(dest => dest.DestValue3,
opt => opt.MapFrom(
src => ConvertToDestValue3(src.SourceValue3, "" //Here I need to pass AnotherSourceDto.AnotherSourceValue2 ))));
}
I'm afraid the answer is to map that property outside AutoMapper.
Here's an example where you could do this using an extension method, so it still has the look and feel of being done with AutoMapper.
// put inside a static class
public static Destination CustomMap(
this IMapper mapper,
SourceDto source,
AnotherSourceDto anotherSource)
{
var destination = mapper.Map<Destination>(source);
destination.DestValue3 =
source.SourceValue3 + " " + anotherSource.AnotherSourceValue2;
return destination;
}
void Main()
{
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<SourceDto, Destination>()
.ForMember(dest => dest.DestValue3, opt => opt.Ignore()));
// usage
var mapper = config.CreateMapper();
var source = new SourceDto { /* add properties */ };
var anotherSource = new AnotherSourceDto { /* add properties */ };
var destination = mapper.CustomMap(source, anotherSource);
}
Is it possible to configure AutoMapper to set all properties to default value in case if the source object is null for specified classes? I know that I should use Mapper.AllowNullDestinationValues = false; to do what I want for all classes in application.
Here the sampled code that I use for tests, but it doesn't work
public class A
{
static A()
{
Mapper.Initialize(
config =>
{
config.ForSourceType<B>().AllowNullDestinationValues = false;
config.CreateMap<B, A>()
.ForMember(member => member.Name, opt => opt.Ignore());
});
//Mapper.AllowNullDestinationValues = false;
Mapper.AssertConfigurationIsValid();
}
public void Init(B b)
{
Mapper.DynamicMap(b, this);
}
public int? Foo { get; set; }
public double? Foo1 { get; set; }
public bool Foo2 { get; set; }
public string Name { get; set; }
}
public class B
{
public string Name { get; set; }
public int? Foo { get; set; }
public double? Foo1 { get; set; }
public bool Foo2 { get; set; }
}
Using of this code:
var b = new B() {Foo = 1, Foo1 = 3.3, Foo2 = true, Name = "123"};
var a = new A {Name = "aName"};
a.Init(b); // All ok: Name=aName, Foo=1, Foo1=3,3, Foo2=True
a.Init(null); // Should be Name=aName, Foo=null, Foo1=null, Foo2=False,
// but a has the same values as on a previous line
It must be related to "a" being already mapped.
var a = new A {Name = "aName"};
a.Init(b);
a.Init(null);
All mappings are cached, so if you'll attempt to re-map same instance the automapper would just keep the original result.
In order to test it, try:
var c = new A {Name = "x"};
c.Init(null);
Here is a link to similar question.
Looks like it will replace with null if you set Mapper.Configuration.AllowNullDestinationValues = false:
public class A
{
static A()
{
Mapper.Initialize(
config =>
{
config.ForSourceType<B>().AllowNullDestinationValues = false;
config.CreateMap<B, A>()
.ForMember(member => member.Name, opt => opt.Ignore());
});
Mapper.Configuration.AllowNullDestinationValues = false;
Mapper.AssertConfigurationIsValid();
}
public void Init(B b)
{
Mapper.DynamicMap(b, this);
}
public int? Foo { get; set; }
public double? Foo1 { get; set; }
public bool Foo2 { get; set; }
public string Name { get; set; }
}