Mapping Viewmodel to model using AutoMapper - c#

I have written a controller method in asp.net api that would return a viewmodel called AllocationsViewModel. The GetAllocationsViewModel contains subsets of three more viewmodels. The GetAllocationsGrouped currently returns FIRMWIDE_MANAGER_ALLOCATION and I need to return this FirmWideAllocationsViewModel instead. I have installed Automapper 8.0 and added some code to do the mapping. Is that enough to do the job. I can see only the ManagerStrategyID and ManagerStrategyID values coming through the values are comming null for the fields. I have run the original query and can see there are values for all the fields
public class FIRMWIDE_MANAGER_ALLOCATION
{
private decimal _groupPercent;
public int FIRM_ID { get; set; }
public string FIRM_NAME { get; set; }
public int? MANAGER_STRATEGY_ID { get; set; }
public int? MANAGER_FUND_ID { get; set; }
public int MANAGER_ACCOUNTING_CLASS_ID { get; set; }
public int? MANAGER_FUND_OR_CLASS_ID { get; set; }
public string MANAGER_FUND_NAME { get; set; }
public string MANAGER_ACCOUNTING_CLASS_NAME { get; set; }
public string MANAGER_STRATEGY_NAME { get; set; }
public int? PRODUCT_ID { get; set; }
public string PRODUCT_NAME { get; set; }
public int? QUANTITY { get; set; }
public decimal? NAV { get; set; }
}
public class FirmWideAllocationsViewModel
{
private decimal _groupPercent;
public int FirmID { get; set; }
public string FirmName { get; set; }
public int? ManagerStrategyID { get; set; }
public int? ManagerFundID { get; set; }
public int ManagerAccountClassID{ get; set; }
public int? ManagerFundOrClassID { get; set; }
public string ManagerFundName { get; set; }
public string ManagerAccountingClassName { get; set; }
public string ManagerStrategyName { get; set; }
public int? ProductID { get; set; }
public string ProductName { get; set; }
public int? Quantity { get; set; }
public decimal? Nav { get; set; }
}
public IHttpActionResult Details(int id, DateTime date)
{
var viewModel = GetAllocationsViewModel(id, date);
if (viewModel == null) return NotFound();
return Ok(viewModel);
}
private AllocationsViewModel GetAllocationsViewModel(int id, DateTime date)
{
var ms = GetStrategy(id);
DateTime d = new DateTime(date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
if (ms.FIRM_ID != null)
{
var firm = GetService<FIRM>().Get(ms.FIRM_ID.Value);
var currentEntity = new EntityAllocationsViewModel(new EntityViewModel { EntityId = firm.ID, EntityName = firm.NAME, EntityType = EntityType.Firm });
var allocationsGrouped = Mapper.Map<List<FIRMWIDE_MANAGER_ALLOCATION>, List<FirmWideAllocationsViewModel>>(GetAllocationsGrouped(EntityType.ManagerStrategy, id, d).ToList());
var missingProducts = GetMissingProducts();
var vm = new AllocationsViewModel
{
CurrentEntity = currentEntity,
ManagerAllocations = allocationsGrouped,
MissingProducts = missingProducts
};
return vm;
}
return null;
}
public class AllocationsViewModel
{
public EntityAllocationsViewModel CurrentEntity { get; set; }
public List<FirmWideAllocationsViewModel> ManagerAllocations { get; set; }
public object MissingProducts { get; set; }
}
I have added the following code after installing autommapper 8.0
public class AutoMapperConfig
{
public static void Initialize()
{
Mapper.Initialize((config) =>
{
config.ReplaceMemberName("FIRM_ID", "FirmID");
config.ReplaceMemberName("FIRM_NAME", "FirmName");
config.ReplaceMemberName("MANAGER_STRATEGY_ID", "ManagerStrategyID");
config.ReplaceMemberName("MANAGER_FUND_ID", "ManagerFundID");
config.ReplaceMemberName("MANAGER_ACCOUNTING_CLASS_ID", "ManagerAccountClassID");
config.ReplaceMemberName("MANAGER_FUND_OR_CLASS_ID", "ManagerFundOrClassID");
config.ReplaceMemberName("MANAGER_FUND_NAME", "ManagerFundName");
config.ReplaceMemberName("MANAGER_ACCOUNTING_CLASS_NAME", "ManagerAccountingClassName");
config.ReplaceMemberName("MANAGER_STRATEGY_NAME", "ManagerStrategyName");
config.ReplaceMemberName("PRODUCT_ID", "ProductID");
config.ReplaceMemberName("PRODUCT_NAME", "ProductName");
config.ReplaceMemberName("QUANTITY", "Quantity");
config.ReplaceMemberName("NAV", "Nav");
config.CreateMap<FIRMWIDE_MANAGER_ALLOCATION, FirmWideAllocationsViewModel>().ReverseMap();
});
}
}
protected void Application_Start()
{
AutoMapperConfig.Initialize();
GlobalConfiguration.Configure(WebApiConfig.Register);
}

The issue has been resolved. I had to amend the grouping statement that is called to include all the fields . It was working fine earlier but with the upgrade of the latest entity framework, I think its the case
allocations = allocations.GroupBy(x => new { x.MANAGER_STRATEGY_ID, x.PRODUCT_ID, x.EVAL_DATE })
.Select(group => new FIRMWIDE_MANAGER_ALLOCATION { EVAL_DATE = group.First().EVAL_DATE,
FIRM_ID = group.First().FIRM_ID,
FIRM_NAME = group.First().FIRM_NAME,
MANAGER_ACCOUNTING_CLASS_ID = group.First().MANAGER_ACCOUNTING_CLASS_ID,
MANAGER_ACCOUNTING_CLASS_NAME = group.First().MANAGER_ACCOUNTING_CLASS_NAME,
MANAGER_FUND_ID = group.First().MANAGER_FUND_ID,
MANAGER_FUND_NAME = group.First().MANAGER_FUND_NAME,
MANAGER_FUND_OR_CLASS_ID = group.First().MANAGER_FUND_OR_CLASS_ID,
NAV = group.First().NAV,
Percent = group.First().Percent,
MANAGER_STRATEGY_ID = group.First().MANAGER_STRATEGY_ID,
EMV = group.Sum(x => x.EMV),
USD_EMV = group.Sum(x => x.USD_EMV),
MANAGER_STRATEGY_NAME = group.First().MANAGER_STRATEGY_NAME,
PRODUCT_ID = group.First().PRODUCT_ID,
PRODUCT_NAME = group.First().PRODUCT_NAME })
.ToList();

Related

Linq query to object cannot pass Xunit test

I have strange problem. I have to pass the following Xunit test:
Fact]
public void GetOrder_ValidId_ReturnsCompleteOrder()
{
var service = new DataService();
var order = service.GetOrder(10248);
Assert.Equal(3, order.OrderDetails.Count);
Assert.Equal("Queso Cabrales", order.OrderDetails.First().Product.Name);
Assert.Equal("Dairy Products", order.OrderDetails.First().Product.Category.Name);
}
I am able to pass the orderdetails list to the test. However the "orderdetail" objects that i pass have a product object with null.
My classes are:
public class Order
{
[Key] public int Id { get; set; }
public DateTime Date { get; set; }
public DateTime Required { get; set; }
[Required] public virtual ICollection<OrderDetail> OrderDetails { get; set; }
public string ShipName { get; set; }
public string ShipCity { get; set; }
public Order()
{
this.OrderDetails = new List<OrderDetail>();
}
public override string ToString()
{
string ret =
$"Id = {Id}, DateTime = {Date}, Required = {Required}, shipName= {ShipName}, Shipcity = {ShipCity}";
return ret;
}
}
public class OrderDetail
{
public Order Order { get; set; }
[ForeignKey("orders")]
public int Orderid { get; set; }
[ForeignKey("products")]
public int productid { get; set; }
public int UnitPrice { get; set; }
public int Quantity { get; set; }
public int Discount { get; set; }
[Required] public virtual Product Product { get; set; }
public OrderDetail()
{
Product = Product;
}
public override string ToString()
{
return
$"OrderId = {Orderid}, Productid = {productid}";
}
}
public class Product
{
[ForeignKey("orderdetails")] public int Id { get; set; }
public string Name { get; set; }
public float UnitPrice { get; set; }
public string QuantityPerUnit { get; set; }
public int UnitsInStock { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
public override string ToString()
{
return
$"Id = {Id}, Name = {Name}, UnitPrice = {UnitPrice}, QuantityPerUnit = {QuantityPerUnit}, UnitsInStock = {UnitsInStock}, CategoryId = {CategoryId}";
}
}
I have the tried the following two solution:
public Order GetOrder(int id)
{
using var ctx = new NorthWindContext();
var query = ctx.Orders.AsQueryable().Where(o => o.Id == 10248).FirstOrDefault();
ctx.SaveChanges();
//var query2 = ctx.Orders.Include("orderdetails").Where()
return query;
}
and
var query2 = ctx.Orders.Where(o => o.Id == 10248)
.Select(a => new Order
{
Id = a.Id,
OrderDetails = a.OrderDetails
}).FirstOrDefault();
I have tried to reconfigure the mapping but didn't do it.
If i do the same query and use Console.Writeline in a foreach loop i can conclude that every orderdetail has a "product"...
I keep on the getting the error: "Object not set to an instance of an object" when passing to xUnit test.
Ivans response did the job.
First of all i removed using System.Data.Entity;
Then i changed the order class to the following:
public class Order
{
[Key] public int Id { get; set; }
public DateTime Date { get; set; }
public DateTime Required { get; set; }
[Required] public virtual List<OrderDetail> OrderDetails { get; set; }
public string ShipName { get; set; }
public string ShipCity { get; set; }
public Order()
{
this.OrderDetails = new List<OrderDetail>();
}
public override string ToString()
{
string ret =
$"Id = {Id}, DateTime = {Date}, Required = {Required}, shipName= {ShipName}, Shipcity = {ShipCity}";
return ret;
}
}
Then i used the following query just like Ivans:
var query3 = ctx.Orders
.Include(o => o.OrderDetails)
.ThenInclude(d => d.Product)
.ThenInclude(d => d.Category)
.AsSingleQuery()
.FirstOrDefault(o => o.Id == 10248);

Entity Framework: Can't save data because of tracking

I have got a complex class. Feedback and Steps. I am using SQL database and .NET Core 2. I can save main properties but can't save the sub class FeedbackSteps properties
public class FeedbackModel
{
[Key]
public int FeedBackID { get; set; }
public DateTime FBDate { get; set; }
public bool? VideoStatus { get; set; }
public string VideoDetail { get; set; }
public string PITFeedBack { get; set; }
public int ActivityID { get; set; }
public virtual ActivityModel Activity { get; set; }
public int ClientID { get; set; }
public virtual ClientModel Client { get; set; }
public int? SupportPlanID { get; set; }
public virtual SupportPlanModel SupportPlan { get; set; }
public int EmployeeID { get; set; }
public virtual Employee Employee { get; set; }
public bool FeedbackStatus { get; set; } = true;
virtual public List<FeedbackStepModel> FeedbackSteps { get; set; }
}
public class FeedbackStepModel
{
[Key]
public int FeedbackStepID { get; set; }
public int FeedbackID { get; set; } = 0;
public int SupportPlanID { get; set; }
public int StepNumber { get; set; }
public string StepDetail { get; set; }
public string AchievementStatus { get; set; }
public string AchievementComment { get; set; }
}
This is the post method. View returns Edited or Updated feedback and i just want to update the database with new data
[HttpPost]
public IActionResult Edit(FeedbackModel feedback)
{
if (ModelState.IsValid)
{
feedbackRepository.Save(feedback);
TempData["message"] = $"Feedback has been saved";
return RedirectToAction("Index");
}
}
After EDIT, I would like to save it...
public void Save(FeedbackModel feedback)
{
if (feedback.FeedBackID == 0)
{
context.FeedbackModels.Add(feedback);
}
else
{
FeedbackModel dbEntry = context.FeedbackModels.Include(s => s.FeedbackSteps).FirstOrDefault(a => a.FeedBackID == feedback.FeedBackID);
if (dbEntry != null)
{
dbEntry.FeedBackID = feedback.FeedBackID;
dbEntry.FBDate = feedback.FBDate;
dbEntry.VideoStatus = feedback.VideoStatus;
dbEntry.VideoDetail = feedback.VideoDetail;
dbEntry.SupportPlanID = feedback.SupportPlanID;
dbEntry.ActivityID = feedback.ActivityID;
dbEntry.PITFeedBack = feedback.PITFeedBack;
dbEntry.ClientID = feedback.ClientID;
dbEntry.EmployeeID = feedback.EmployeeID;
dbEntry.FeedbackStatus = feedback.FeedbackStatus;
dbEntry.FeedbackSteps = feedback.FeedbackSteps;
}
}
context.SaveChanges();
}
But I get this error all the time
The instance of entity type 'FeedbackStepModel' cannot be tracked because another instance with the key value '{FeedbackStepID: 1}' is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Your FeedbackModel update operation with children (FeedbackSteps) should be as follows:
FeedbackModel dbEntry = context.FeedbackModels.Include(s => s.FeedbackSteps).FirstOrDefault(a => a.FeedBackID == feedback.FeedBackID);
if (dbEntry != null)
{
dbEntry.FeedBackID = feedback.FeedBackID;
dbEntry.FBDate = feedback.FBDate;
dbEntry.VideoStatus = feedback.VideoStatus;
dbEntry.VideoDetail = feedback.VideoDetail;
dbEntry.SupportPlanID = feedback.SupportPlanID;
dbEntry.ActivityID = feedback.ActivityID;
dbEntry.PITFeedBack = feedback.PITFeedBack;
dbEntry.ClientID = feedback.ClientID;
dbEntry.EmployeeID = feedback.EmployeeID;
dbEntry.FeedbackStatus = feedback.FeedbackStatus;
dbEntry.FeedbackSteps.Clear(); // First you have to clear the existing feedBackSteps
foreach(FeedbackStep feedBackStep in feedback.FeedbackSteps)
{
dbEntry.FeedbackSteps.Add(feedBackStep); // You have to add new and updated feedBackStep here.
}
}
If dbEntry.FeedbackSteps.Clear(); does not work (may be in EF Core 2.0 or lower Clear() does not work) then replace dbEntry.FeedbackSteps.Clear(); with the following code:
foreach(FeedbackStep feedbackStepToBeRemoved in dbEntry.FeedbackSteps)
{
context.Remove(feedbackStepToBeRemoved);
}

How To Process Entity Framework Context int Lambda Expression

I using ef 6.13 to get some data, and return a new class data to my app.
public List<v_apply_detail2> GetApplyList()
{
using (yoyoEntities ds = new yoyoEntities())
{
return datas = ds.v_apply_detail.Select(p => new v_apply_detail2 { apply_id = p.apply_id, time_line = ds.yoyo_apply_timeline.ToList() }).ToList();
}
}
It's runing normal.
and I want to move the Select method in a New Func, and using the dbcontext yoyoEntities(ds).
Func<v_apply_detail, v_apply_detail2> SelectOutApply(yoyoEntities ds, bool includetimeline = false)
{
return (p => new v_apply_detail2
{
apply_id = p.apply_id,
time_line = includetimeline ? ds.yoyo_apply_timeline.Where(x => x.apply_id == p.apply_id).OrderByDescending(x => x.time).ToList() : null
});
}
public List<v_apply_detail2> GetApplyList()
{
using (yoyoEntities ds = new yoyoEntities())
{
return datas = ds.v_apply_detail.Select(SelectOutApply(ds,true)).ToList();
}
}
When I call the GetApplyList function , it's get a error :Entity Framework: There is already an open DataReader associated with this Connection which must be closed first.
How do I can using the dbcontext in Func<> or Expression<> ?
think guys!
Allowing Multiple Active result sets should fix the error that you are seeing.
string connectionString = "Data Source=MSSQL1;" +
"Initial Catalog=AdventureWorks;Integrated Security=SSPI;" +
"MultipleActiveResultSets=True";
https://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.110).aspx
But I believe you should be passing a queryable rather than a dbcontext.
Update:
Make the Timeline property Virtual.
public partial class v_apply_detail2 {
public long apply_id { get; set; }
public long hiring_id { get; set; }
public string hiring_title { get; set; }
public string publish_user_id { get; set; }
public string publish_company_id { get; set; }
public string user_id { get; set; }
public Nullable<System.DateTime> pre_accept_time { get; set; }
public sbyte join_type { get; set; }
public Nullable<decimal> deal_pay { get; set; }
public Nullable<System.DateTime> deal_time { get; set; }
public Nullable<System.DateTime> start_work_time { get; set; }
public Nullable<System.DateTime> finish_time { get; set; }
public string master_appraise { get; set; }
public string worker_appraise { get; set; }
public Nullable<int> master_rating { get; set; }
public Nullable<int> worker_rating { get; set; }
public int status { get; set; }
public Nullable<System.DateTime> master_rating_time { get; set; }
public Nullable<System.DateTime> worker_rating_time { get; set; }
public string hiring_snapshots { get; set; }
public virtual List<yoyo_apply_timeline> time_line { get; set; }
public member_info member_info { get; set; }
public company_info company_info { get; set; }}
Then instead of using context use the query variable:
Func<v_apply_detail, v_apply_detail2> SelectOutApply(yoyoEntities ds, bool includetimeline = false) {
return (p => new v_apply_detail2
{
apply_id = p.apply_id,
time_line = includetimeline ? p.timeline.OrderByDescending(x => x.time) : null
});}
Better way (single method):
Just to keep things simple the below should work well (just don't use datacontext to query timelines again):
public List<apply_detail_dto> GetApplyList(){
using (yoyoEntities ds = new yoyoEntities())
{
return datas = ds.v_apply_detail.Select(p => new apply_detail_dto { apply_id = p.apply_id, time_line = p.timeline }).ToList();
}}
And use a DTO object to project into (new apply_detail_dto {}).
Hope that helps.
this is may be helpful you
Expression<Func<v_apply_detail, v_apply_detail2>> SelectOutApply(yoyoEntities ds,bool includetimeline = false)
{
if (includetimeline)
return (p) => new v_apply_detail2
{
apply_id = p.apply_id,
time_line = ds.yoyo_apply_timeline.Where(x => x.apply_id == p.apply_id).OrderByDescending(x => x.time).ToList()
};
return (p) => new v_apply_detail2
{
apply_id = p.apply_id
};
}
public List<v_apply_detail2> GetApplyList()
{
using (yoyoEntities ds = new yoyoEntities())
{
return datas = ds.v_apply_detail.Select(ds,SelectOutApply(true)).ToList();
}
}
Thank Amanvir Singh Mundra.
here is my poco classes
public partial class v_apply_detail2
{
public long apply_id { get; set; }
public long hiring_id { get; set; }
public string hiring_title { get; set; }
public string publish_user_id { get; set; }
public string publish_company_id { get; set; }
public string user_id { get; set; }
public Nullable<System.DateTime> pre_accept_time { get; set; }
public sbyte join_type { get; set; }
public Nullable<decimal> deal_pay { get; set; }
public Nullable<System.DateTime> deal_time { get; set; }
public Nullable<System.DateTime> start_work_time { get; set; }
public Nullable<System.DateTime> finish_time { get; set; }
public string master_appraise { get; set; }
public string worker_appraise { get; set; }
public Nullable<int> master_rating { get; set; }
public Nullable<int> worker_rating { get; set; }
public int status { get; set; }
public Nullable<System.DateTime> master_rating_time { get; set; }
public Nullable<System.DateTime> worker_rating_time { get; set; }
public string hiring_snapshots { get; set; }
public List<yoyo_apply_timeline> time_line { get; set; }
public member_info member_info { get; set; }
public company_info company_info { get; set; }
}
i has same class in my project to provide data service
so, my Problem is the query performance and Code reuse.
if i add the dbcontext in my select func ,like this
using (yoyoEntities dd = new yoyoEntities())
{
v_apply_detail2 result = new v_apply_detail2();
{
result.apply_id = x.apply_id;
result.time_line = dd.yoyo_apply_timeline.Where(p => p.apply_id == x.apply_id).OrderByDescending(p => p.time).ToList();
}
return result;
}
i can get the result , but the trigger many sql query .
but use
return datas = ds.v_apply_detail.Select(p => new v_apply_detail2 { apply_id = p.apply_id, time_line = ds.yoyo_apply_timeline.ToList() }).ToList();
the sql query only one。so ,i want to reuse the ds。i think the expression can do this,and i can't find the road.

IEnumerable of object returns JSON, but IEnumerable of similar object does not

I have two classes that are similar. I'm trying to see if I can simply use one. The first is in our DataModel assembly, and used in our DbContext.
public partial class type210_MotorCarrierFreightInvoice
{
public int MotorCarrierFreightInvoiceId { get; set; } // MotorCarrierFreightInvoiceId (Primary key)
public string InvoiceId { get; set; } // InvoiceId
public DateTime BillDate { get; set; } // BillDate
public string TrackingNumber { get; set; } // TrackingNumber
public decimal NetShipmentCharge { get; set; } // NetShipmentCharge
public long InterchangeTransactionDetailId { get; set; } // InterchangeTransactionDetailId
public long? BillToAddressId { get; set; } // BillToAddressId
public long? ConsigneeAddressId { get; set; } // ConsigneeAddressId
public long? ShipperAddressId { get; set; } // ShipperAddressId
// Foreign keys
public virtual interchange_TransactionDetail interchange_TransactionDetail { get; set; } // fk_Type210_MotorCarrierFreightInvoice_Interchange_TransactionDetail
public virtual typeAny_Address typeAny_Address_BillToAddressId { get; set; }
public virtual typeAny_Address typeAny_Address_ConsigneeAddressId { get; set; }
public virtual typeAny_Address typeAny_Address_ShipperAddressId { get; set; }
public type210_MotorCarrierFreightInvoice()
{
InitializePartial();
}
partial void InitializePartial();
}
The second class should represent the same object.
public class Type210MotorCarrierFreightInvoice
{
public int MotorCarrierFreightInvoiceId { get; set; }
public string InvoiceId { get; set; }
public DateTime BillDate { get; set; }
public string TrackingNumber { get; set; }
public decimal NetShipmentCharge { get; set; }
public long InterchangeTransactionDetailId { get; set; }
public Address BillToAddress { get; set; }
public Address ConsigneeAddress { get; set; }
public Address ShipperAddress { get; set; }
}
I'm working with a WebApi service that will expose the data via JSON. Basically what I'm trying is this:
[Route("api/Subscriber/Get210sNew/{transactionId}")]
[Route("api/Subscriber/Get210sNew/{transactionId}/{limit}")]
public IEnumerable<type210_MotorCarrierFreightInvoice> Get210sDOES_NOT_WORK(int transactionId, int limit = DefaultLimit)
{
var key = new QueryAfterId(transactionId, limit);
var dataService = new Type210DataService(_ediContext);
return dataService
.Get(key)
.Select(x => x);
}
[Route("api/Subscriber/Get210s/{transactionId}")]
[Route("api/Subscriber/Get210s/{transactionId}/{limit}")]
public IEnumerable<type210_MotorCarrierFreightInvoice> Get210sWorks(int transactionId, int limit = DefaultLimit)
{
var key = new QueryAfterId(transactionId, limit);
var dataService = new Type210DataService(_ediContext);
return dataService
.Get(key)
.Select(Map);
}
Here is the Map method:
private static Type210MotorCarrierFreightInvoice Map(
type210_MotorCarrierFreightInvoice dbNotice)
{
return new Type210MotorCarrierFreightInvoice
{
MotorCarrierFreightInvoiceId = dbNotice.MotorCarrierFreightInvoiceId,
InvoiceId = dbNotice.InvoiceId,
BillDate = dbNotice.BillDate,
ShipDate = dbNotice.ShipDate,
TrackingNumber = dbNotice.TrackingNumber,
PurchaseOrderNumber = dbNotice.PurchaseOrderNumber,
BillOfLading = dbNotice.BillOfLading,
NetShipmentCharge = dbNotice.NetShipmentCharge,
InterchangeTransactionDetailId = dbNotice.InterchangeTransactionDetailId,
BillToAddress = Mapper.MapToAddress(dbNotice.typeAny_Address_BillToAddressId),
ConsigneeAddress = Mapper.MapToAddress(dbNotice.typeAny_Address_ConsigneeAddressId),
ShipperAddress = Mapper.MapToAddress(dbNotice.typeAny_Address_ShipperAddressId)
};
}
Get210sWorks will return Json data to the browser. Get210sDOES_NOT_WORK does not. Both queries get the same number of records. But Get210sDOES_NOT_WORK shows no records in the browser window.
Why can't I return an IEnumerable of my DataModel class as Json?

Using Linq to pass data from one collection to another

I want to use LINQ to pass data from one custom collection to another. Its complicated because the collection has 2 sub collections.
Want to copy data to:
public class Quote
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public decimal Price { get; set; }
}
from:
public class Quote
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
I was getting somewhere with this but keeping hitting problems...
newQuotes = Quotes
.Select(x => new Quote() {
Id = x.QuoteId,
Rates = x.RateSets.Select( y => new Rate() {
Id = y.Id,
Options = y.Options.Select(z => new Option() {
Id = z.Id,
Price = z.Price
}).ToList(),....
to
Compiled without any errors
// to
public class Quote2
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option2> Options { get; set; }
}
public class Option2
{
public int Id { get; set; }
public decimal Price { get; set; }
}
// from
public class Quote1
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option1> Options { get; set; }
}
public class Option1
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
void Main()
{
var Quotes = new List<Quote1>();
var newQuotes = Quotes
.Select(x => new Quote2 {
Id = x.QuoteId,
Rates = x.RateSets == null ? null : x.RateSets.Select( y => new Rate {
Id = y.Id,
Options = y.Options == null ? null : y.Options.Select(z => new Option2 {
Id = z.Id,
Price = z.Price.Value
}).ToList()}).ToList()}).ToList();
}
I would make it a bit more modular:
newQuotes = Quotes.Select(x => new Quote
{
ID = x.QuoteID,
Type = x.Type,
Rates = ConvertRates(x.RateSets)
});
ConvertRates would use the same approach to create its sub objects and could either be a method or a Func:
ICollection<Rate> ConvertRates(IEnumerable<RateSet> oldRates)
{
return oldRates.Select(x => new Rate
{
ID = x.ID,
Options = ConvertOptions(x.Options)
}).ToList();
}
Basically, this is the same approach you used, just split up and readable.
I think what you need to do is define casting between each two corresponding classes, then cast one list into the other.
A simpler way may be to create methods in each class that would convert itself to the other type. Or if you don't want that kind of coupling, create a factory class that will do the conversion for you, one item at a time. Then use link to loop through and convert each item.
Like so:
public class Quote
{
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
public static Quote FromData(Data.Quote input){
if (input == null) return null;
Quote output = new Quote()
{
Id = input.QuoteId,
Type = input.Type
};
output.Rates = (from i in input.RateSets
select Rate.FromData(i)).ToList();
}
}
public class Rate
{
public int Id { get; set; }
public virtual ICollection<Option> Options { get; set; }
public static Rate FromData(Data.RateSet input)
{
if (input == null) return null;
Rate output = new Rate()
{
Id = input.Id
};
output.Options = (from i in input.Options
select Option.FromData(i)).ToList();
return output;
}
}
public class Option
{
public int Id { get; set; }
public decimal Price { get; set; }
public static Option FromData(Data.Option input)
{
if (input == null) return null;
Option output = new Option()
{
Id = input.Id,
Price = input.Price ?? 0m
};
return output;
}
}
namespace Data {
public class Quote
{
public int QuoteId { get; set; }
public string Type { get; set; }
public string Destination { get; set; }
public List<RateSet> RateSets { get; set; }
}
public class RateSet
{
public int Id { get; set; }
public decimal ValueMin { get; set; }
public decimal ValueMax { get; set; }
public List<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
}
}

Categories