Linq to xml select new PurchaseOrder - linq

I'm trying to get my head arround "Linq to xml" so let me jump right to it. I have this xml :
<?xml version="1.0"?>
<PurchaseOrders>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
</PurchaseOrders>
And then I created som simple objects :
public class PurchaseOrder
{
public string PurchaseOrderNumber { get; set; }
public string OrderDate { get; set; }
public Address BillingAddress { get; set; }
public Address ShippingAddress { get; set; }
public string DeliveryNotes { get; set; }
public List<Item> Items { get; set; }
}
public class Address
{
public string Type { get; set; }
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
public class Item
{
public string PartNumber { get; set; }
public string ProductName { get; set; }
public string Quantity { get; set; }
public string USPrice { get; set; }
public string Comment { get; set; }
}
Then i go ahead an make my linq :
var orders = (from order in XDocument.Load(Server.MapPath("/App_Data/Order.xml")).Descendants("PurchaseOrder")
select new PurchaseOrder
{
PurchaseOrderNumber = order.Attribute("PurchaseOrderNumber").Value,
OrderDate = order.Attribute("OrderDate").Value,
BillingAddress = new Address
{
City = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("City").Value,
Country = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Country").Value,
Name = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Name").Value,
State = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("State").Value,
Street = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Street").Value,
Type = "Billing",
Zip = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Zip").Value
},
ShippingAddress = new Address
{
City = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("City").Value,
Country = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Country").Value,
Name = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Name").Value,
State = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("State").Value,
Street = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Street").Value,
Type = "Shipping",
Zip = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Zip").Value
},
DeliveryNotes = order.Element("DeliveryNotes").Value,
Items = null
});
First of, why does the "order.Element("DeliveryNotes").Value" not exists in the output xml and throws "Object reference not set to an instance of an object" ?
Second, how do I get the "Type" (which is an Attribute) for the address?
Third, how do I make the list of items this one "Items = null" so that I have a fully functional object to work with ?
And finally if the rest of the linq needs som tuning then go ahead and guid me in the right direction ;o)

UPDATE
I've rewrited code so it do not using value property now. It also should populate your Items list.
I've found this topics very useful:
LINQ to XML optional element query
How do you guard for Null Reference exceptions in Linq To Xml?
var doc = XDocument.Load(#"C:\Temp\stackoverflow.xml");
var ordersTemp =
(from order in doc.Descendants("PurchaseOrder") select
new {
order,
BillingAddress = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single(),
ShippingAddress = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single(),
Items = order.Descendants("Items").Descendants("Item")
});
var orders = (from order in ordersTemp select
new PurchaseOrder
{
PurchaseOrderNumber = (string)order.order.Attribute("PurchaseOrderNumber"),
OrderDate = (string)order.order.Attribute("OrderDate"),
BillingAddress = new Address
{
City = (string)order.BillingAddress.Element("City"),
Country = (string)order.BillingAddress.Element("Country"),
Name = (string)order.BillingAddress.Element("Name"),
State = (string)order.BillingAddress.Element("State"),
Street = (string)order.BillingAddress.Element("Street"),
Type = "Billing",
Zip = (string)order.BillingAddress.Element("Zip")
},
ShippingAddress = new Address
{
City = (string)order.ShippingAddress.Element("City"),
Country = (string)order.ShippingAddress.Element("Country"),
Name = (string)order.ShippingAddress.Element("Name"),
State = (string)order.ShippingAddress.Element("State"),
Street = (string)order.ShippingAddress.Element("Street"),
Type = "Shipping",
Zip = (string)order.ShippingAddress.Element("Zip")
},
DeliveryNotes = (string)order.order.Element("DeliveryNotes"),
Items = (from item in order.Items select new Item
{
PartNumber = (string)item.Attribute("PartNumber"),
ProductName = (string)item.Element("ProductName"),
Quantity = (string)item.Element("Quantity"),
USPrice = (string)item.Element("USPrice"),
Comment = (string)item.Element("Comment")
}).ToList()
});

Related

How and where to use AddRange() method

I want to display related data from second table with each value in first table
i have tried this query
public ActionResult Index()
{
List<EmployeeAtt> empWithDate = new List<EmployeeAtt>();
var employeelist = _context.TblEmployee.ToList();
foreach (var employee in employeelist)
{
var employeeAtt = _context.AttendanceTable
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
Date = g.Key,
Emp_name = employee.EmployeeName,
InTime = g.Any(e => e.ScanType == "I") ? g.Where(e =>
e.ScanType == "I").Min(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent",
OutTime = g.Any(e => e.ScanType == "O") ? g.Where(e =>
e.ScanType == "O").Max(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent"
});
empWithDate.AddRange(employeeAtt);
}
return View(empWithDate);
}
Here is my attendance Table
AttendanceTable
Results
I want to display the shortest time with "I" Column value against each employee and last time with "O" Column value as out time. I think i am not using AddRange() at proper place. Where it should go then?
public partial class TblEmployee
{
public TblEmployee()
{
AttendanceTable = new HashSet<AttendanceTable>();
}
public int EmpId { get; set; }
public string EmployeeName { get; set; }
public virtual ICollection<AttendanceTable> AttendanceTable { get; set; }
}
public partial class AttendanceTable
{
public int Id { get; set; }
public int AttendanceId { get; set; }
public int EmployeeId { get; set; }
public string ScanType { get; set; }
public DateTime DateAndTime { get; set; }
public virtual TblEmployee Employee { get; set; }
}
The actual problem is not related to AddRange(), you need a where clause before GroupBy() to limit attendances (before grouping) to only records related to that specific employee, e.g.
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
...
Depended on your model, it is better to use some kind of ID instead of EmployeeName for comparison if possible.
Also you can use SelectMany() instead of for loop and AddRange() to combine the results into a single list. like this:
List<EmployeeAtt> empWithDate = _context.TblEmployee.ToList()
.SelectMany(employee =>
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
...
})
);
...

Dynamic LINQ: Comparing Nested Data With Parent Property

I've a class with following structure:
public class BestWayContext
{
public Preference Preference { get; set; }
public DateTime DueDate { get; set; }
public List<ServiceRate> ServiceRate { get; set; }
}
public class ServiceRate
{
public int Id { get; set; }
public string Carrier { get; set; }
public string Service { get; set; }
public decimal Rate { get; set; }
public DateTime DeliveryDate { get; set; }
}
and I've dynamic linq expression string
"Preference != null && ServiceRate.Any(Carrier == Preference.Carrier)"
and I want to convert above string in Dynamic LINQ as follows:
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, null).Compile();
But it showing following error:
Please correct me what am I doing wrong?
It looks like you wanted to do something like this:
var bwc = new BestWayContext
{
Preference = new Preference { Carrier = "test" },
DueDate = DateTime.Now,
ServiceRate = new List<ServiceRate>
{
new ServiceRate
{
Carrier = "test",
DeliveryDate = DateTime.Now,
Id = 2,
Rate = 100,
Service = "testService"
}
}
};
string condition = "Preference != null && ServiceRate.Any(Carrier == #0)";
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, bwc.Preference.Carrier).Compile();
bool res = expression(bwc); // true
bwc.ServiceRate.First().Carrier = "test1"; // just for testing this -> there is only one so I've used first
res = expression(bwc); // false
You want to use Preference which belong to BestWayContext but you didn't tell the compiler about that. If i write your expression on Linq i will do as follows:
[List of BestWayContext].Where(f => f.Preference != null && f.ServiceRate.Where(g => g.Carrier == f.Preference.Carrier)
);
As you see i specified to use Preference of BestWayContext.

Group records by id

I am getting records like this returned from a stored procedure:
Id FullName Review Rating
---------------------------------------------
1 john sk 4.5
1 john hhh 3.5
1 john jhj 1.5
2 rig www 3.5
2 rig eee 1.5
This is my query which return records like above:
var empDetails = context.Database.SqlQuery<SearchWorkerDetail>("exec SearchWorkerDetail #param1", new SqlParameter("param1", searchKeyword) )
.Select( d => new
{
id = d.Id,
FullName = d.FullName,
Email = d.Email,
ServiceDescription = d.ServiceDescription,
Skills = d.Skills,
Name = d.Name,
r = d.Review,
averagerating = d.Rating
}).ToList();
Now I want to group the records by Id and want to select data.
Expected output:
SearchWorkerDetail=
[0]:{
Id:1
FullName:John
Email:john#yahoo.com
ReviewList:
{
[0]: Review=sk
rating=4.5
[1]: Review=hhh
rating=3.5
[2]: Review=jhj
rating=1.5
}
[1]:{
Id:2
FullName:rig
Email:rig#yahoo.com
ReviewList:
{
[0]: Review=www
rating=3.5
[1]: Review=eee
rating=1.5
}
}
For corresponding id I want list of reviews (1 to many relation between worker and review)
My class structure:
[DataContract]
public class SearchWorkerDetail
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FullName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string ServiceDescription { get; set; }
[DataMember]
public string Skills { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Review { get; set; }
[DataMember]
public Nullable<decimal> Rating { get; set; }
[DataMember]
public List<ReviewModel> RatingList { get; set; }
}
My query is like this:
var data = empDetails.GroupBy(m=>m.id)
.Select(g => new SearchWorkerDetail
{
id =g.Key,
FullName = g.FullName,
Reviewlist =
}
).ToList();
Here it is not allowing me to select FullName and other (eg: Email, skillDescription, Skills etc)
How do I do this?
There is no FullName property on g because it's a group not a single object. However you can get the FullName of first record in the group then create a ReviewList for each record in the group by using another Select:
empDetails.GroupBy(m => m.id)
.Select(g => new SearchWorkerDetail
{
id = g.Key,
FullName = g.First().FullName,
Reviewlist= g.Select(x => new ReviewModel
{
Review = x.Review,
rating = x.Rating
}.ToList()
}).ToList();
Or you can also group by based on FullName:
empDetails.GroupBy(m => new { m.id, m.FullName })
.Select(g => new SearchWorkerDetail
{
id = g.Key.id,
FullName = g.Key.FullName,
/* rest is the same */
}).ToList();

Get first object in child collection inside projection

I have the following entities:
public class Mark {
public Int32 Id { get; set; }
public DateTime Created { get; set; }
public virtual ICollection<MarkLocalized> MarksLocalized { get; set; }
} // Mark
public class MarkLocalized {
public Int32 Id { get; set; }
public String Culture { get; set; }
public String Name { get; set; }
public virtual Mark Mark { get; set; }
} // MarkLocalized
Given a culture, for example "en", I need to get all Marks, their localized name for the given culture and map them to a MarkModel:
public class MarkModel {
public Int32 Id { get; set; }
public String Name { get; set; }
} // MarkModel
I tried the following:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized, (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But I need only the MarksLocalized which culture is equal to the given culture.
How can I do this?
Thank you,
Miguel
You can add Where inside SelectMany:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized.Where(x => x.Culture == "en"), (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But should be more clear with syntax query:
from m in new Context.Marks
from l in m.MarksLocalized
where l.Culture == "en"
select new MarkModel { m.ID, m.Name, l.Slug }
which should be translated into following query by compiler:
context.Marks
.SelectMany(x => x.MarksLocalized, (m, l) => new { m, l })
.Where(x => x.l.Culture == "en")
.Select(x => new MarkModel { x.m.ID, x.m.Name, x.l.Slug })
which should produce exact same results as first one.

How can I add more than 2 conditions to the LlNQ where clause?

I have a LINQ query with more than 2 where conditions, but it doesn't seem to evaluate with more than 2 conditions. Is there a way to add more conditions to the where clause?
var query =
from f in XElement.Load(MapPath("flightdata3.xml")).Elements("flight")
where (string)f.Element("departurelocation") == From &&
(string)f.Element("destinationlocation") == DestCity &&
(string)f.Element("airline") == Airline
// && (string)f.Element("departuredate") == DepartDate &&
// (string)f.Element("departuretime")==DepartTime
//&& (string)f.Element("returndate")==ReturnDate &&
//(string)f.Element("returntime")==ReturnTime
orderby Convert.ToInt32(f.Element("price").Value)
select new
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
LINQ absolutely allows more than two WHERE conditions. Have you tried separating the query into more manageable pieces? LINQ uses deferred execution anyway so you won't see a performance penalty in doing so.
You should also consider making a class to hold the information you're stuffing into the result.
public class FlightDetail
{
public Int32 FlightNumber { get; set; }
public String Airline { get; set; }
public String Departure { get; set; }
public String DepartureTime { get; set; }
public String Destination { get; set; }
public String ArrivalTime { get; set; }
public Int32 Stops { get; set; }
public String Duration { get; set; }
public String Cabin { get; set; }
public Int32 Price { get; set; }
public String ImagePath { get; set; }
}
Then something like this which is more readable but should also help you find whatever bug is popping up.
var flights =
from f in XElement.Load(MapPath("flightdata3.xml")).Elements("flight")
select new FlightDetail
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
var flightsByLocation =
flights.
where (string)f.Element("departurelocation") == From &&
(string)f.Element("destinationlocation") == DestCity
select new FlightDetail
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
There shouldn't be an issue with having more then one condition. For example, you could have something like this from an Order table.
var orderDetails = (from o in context.OrderDetails
where o.OrderID == orderID
where o.OrderName == orderName
select o).ToList();

Resources