I'm new in AutoMapper and I'm trying to map list to list in this way:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
public class PersonMin
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<List<Person>, List<PersonMin>>();
});
IMapper iMapper = config.CreateMapper();
List<Person> source = new List<Person>
{
new Person { Id = 1, FirstName = "Bob", LastName = "Davis", Address = "Street1", Age = 40},
new Person { Id = 2, FirstName = "Rob", LastName = "Mavis", Address = "Street2", Age = 42}
};
List<PersonMin> destination = iMapper.Map<List<Person>, List<PersonMin>>(source);
foreach (var item in destination)
{
Console.WriteLine(item.Id + ", " + item.FirstName + ", " + item.LastName);
}
The destination is empty.
You don't need to care about the list.
Just simply map the models
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Person, PersonMin>();
});
Related
I am having problems populating data grid combo box column with combo boxes, the data grid column is not showing any data. But, first things first. Here are my binding classes:
public class StartingEleven
{
public string name { get; set; }
public int shirt_number { get; set; }
public string position { get; set; }
public bool captain { get; set; }
}
public class DGObject
{
public string FifaID { get; set; }
public string Venue { get; set; }
public string Location { get; set; }
public DateTime Date { get; set; }
public int Goals { get; set; }
public int Penalties { get; set; }
public string AwayTeamCode { get; set; }
public int GuestGoals { get; set; }
public List<StartingEleven> HomeTeam11 { get; set; }
}
And my root object:
public class RootObject
{
public string venue { get; set; }
public string location { get; set; }
public string status { get; set; }
public string time { get; set; }
public string fifa_id { get; set; }
public Weather weather { get; set; }
public string attendance { get; set; }
public List<string> officials { get; set; }
public string stage_name { get; set; }
public string home_team_country { get; set; }
public string away_team_country { get; set; }
public DateTime datetime { get; set; }
public string winner { get; set; }
public string winner_code { get; set; }
public HomeTeam home_team { get; set; }
public AwayTeam away_team { get; set; }
public List<HomeTeamEvent> home_team_events { get; set; }
public List<AwayTeamEvent> away_team_events { get; set; }
public HomeTeamStatistics home_team_statistics { get; set; }
public AwayTeamStatistics away_team_statistics { get; set; }
public DateTime last_event_update_at { get; set; }
public DateTime last_score_update_at { get; set; }
}
There is a saying: a single picture is worth a thousand words... So, to be more accurate, first, here is my output that I am having problems with:
Here is my method that loads this datagrid:
public static void LoadAllEventsForHomeTeam(System.Windows.Controls.ComboBox cb, System.Windows.Controls.DataGrid dg)
{
var url = new Url("http://worldcup.sfg.io/matches/country?fifa_code=");
string urlEndpoint = GetItemFromComboBoxWpf(cb);
var request = url + urlEndpoint;
string cbItem = cb.SelectedItem.ToString();
System.Windows.Controls.DataGridTextColumn c1 = new System.Windows.Controls.DataGridTextColumn();
c1.Header = "Game ID";
c1.Binding = new System.Windows.Data.Binding("FifaID");
c1.Width = 120;
dg.Columns.Add(c1);
System.Windows.Controls.DataGridTextColumn c2 = new System.Windows.Controls.DataGridTextColumn();
c2.Header = "City";
c2.Binding = new System.Windows.Data.Binding("Venue");
c2.Width = 95;
dg.Columns.Add(c2);
System.Windows.Controls.DataGridTextColumn c3 = new System.Windows.Controls.DataGridTextColumn();
c3.Header = "Location";
c3.Binding = new System.Windows.Data.Binding("Location");
c3.Width = 150;
dg.Columns.Add(c3);
System.Windows.Controls.DataGridTextColumn c4 = new System.Windows.Controls.DataGridTextColumn();
c4.Header = "Date";
c4.Binding = new System.Windows.Data.Binding("Date");
c4.Width = 180;
dg.Columns.Add(c4);
System.Windows.Controls.DataGridTextColumn c5 = new System.Windows.Controls.DataGridTextColumn();
c5.Header = "Goals";
c5.Binding = new System.Windows.Data.Binding("Goals");
c5.Width = 75;
dg.Columns.Add(c5);
System.Windows.Controls.DataGridTextColumn c6 = new System.Windows.Controls.DataGridTextColumn();
c6.Header = "Penalties";
c6.Binding = new System.Windows.Data.Binding("Penalties");
c6.Width = 100;
dg.Columns.Add(c6);
System.Windows.Controls.DataGridTextColumn c7 = new System.Windows.Controls.DataGridTextColumn();
c7.Header = "Guest";
c7.Binding = new System.Windows.Data.Binding("AwayTeamCode");
c7.Width = 90;
dg.Columns.Add(c7);
System.Windows.Controls.DataGridTextColumn c8 = new System.Windows.Controls.DataGridTextColumn();
c8.Header = "Guest score";
c8.Binding = new System.Windows.Data.Binding("GuestGoals");
c8.Width = 110;
dg.Columns.Add(c8);
System.Windows.Controls.DataGridComboBoxColumn c9 = new System.Windows.Controls.DataGridComboBoxColumn();
c9.Header = "Team 11";
c9.TextBinding = new System.Windows.Data.Binding("HomeTeam11");
c9.Width = 110;
dg.Columns.Add(c9);
try
{
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(async () =>
// await Task.Run(async () =>
{
if (request != null)
{
List<Teams.RootObject> matches = await request.GetJsonAsync<List<Teams.RootObject>>();
foreach (var match in matches)
{
if (cbItem.Contains(match.home_team.code))
{
dg.Items.Add(new Teams.DGObject
{
FifaID = match.fifa_id,
Venue = match.venue,
Location = match.location,
Date = match.datetime.ToLocalTime(),
Goals = match.home_team.goals,
Penalties = match.home_team.penalties,
AwayTeamCode = match.away_team.code,
GuestGoals = match.away_team.goals,
HomeTeam11 = match.home_team_statistics.starting_eleven
});
}
}
}
}));
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
As you can see, this method loads datagrid with events which are soccer matches for selected team. By selecting an item from combo box, here named cb, a method forms an API call by completing an API call with selected combo box item as endpoint thus loading datagrid with matches that Germany played as a home team. Now, here is the problem: Last column, here named Team11 should contain combo boxes, and combo boxes should contain eleven players (starting eleven) that played that match. Selected team, in this case Germany, obviously, played four matches as home team. So, in column Team11 there should be four comboboxes, one for each game, placed in each row, and each combobox should contain eleven players that played that particular match, but there is no data in column Team11. Any ideas? Thank you.
I'm trying to come up with a car service booking application that allows one to either book a car into a service as well as buy a few parts, which is not essential, but I get an error that reads as follows:
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.BasketLines_dbo.Parts_PartID". The conflict occurred in database "aspnet-Noir-20190224082924", table "dbo.Parts", column 'PartId'.
The statement has been terminated.
My classes are as follows:
PART
public class Part
{
[Key]
public int PartId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public byte[] ImageFile { get; set; }
public string ImageFilePath { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ServicePartMapping>
ServicePartMappings { get; set;}
}
Service
public class Service
{
public int ServiceId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public decimal Price { get; set; }
public ICollection<Part> Parts { get; set; }
}
ServicePartMapping
public class ServicePartMapping
{
public int ServicePartMappingID { get; set; }
public int PartNumber { get; set; }
public int? ServiceId { get; set; }
public int? ServicePartId { get; set; }
public virtual Service Service { get; set; }
public virtual ServicePart ServicePart { get;
set; }
}
Basket
public class Basket
{
public int Id { get; set; }
private string BasketID { get; set; }
private const string BasketSessionKey =
"BasketID";
private ApplicationDbContext db = new
ApplicationDbContext();
private string GetBasketID()
{
if
(HttpContext.Current.Session[BasketSessionKey]
== null)
{
if
(!string.IsNullOrWhiteSpace
(HttpContext.Current
.User.Identity.Name))
{
HttpContext.Current
.Session[BasketSessionKey] =
HttpContext.Current
.User.Identity.Name;
}
else
{
Guid tempBasketID = Guid.NewGuid()
HttpContext.Current
.Session[BasketSessionKey]
= tempBasketID.ToString();
}
}
return
HttpContext.Current
.Session[BasketSessionKey].ToString();
}
public static Basket GetBasket()
{
Basket basket = new Basket();
basket.BasketID = basket.GetBasketID();
return basket;
}
public void AddServiceToBasket(int serviceID,
int quantity)
{
var basketLine =
db.BasketLines.FirstOrDefault(b =>
b.BasketID == BasketID && b.ServiceID
== serviceID);
if (basketLine == null)
{
basketLine = new BasketLine
{
ServiceID = serviceID,
BasketID = BasketID,
Quantity = quantity,
DateCreated = DateTime.Now
};
db.BasketLines.Add(basketLine);
}
else
{
basketLine.Quantity += quantity;
}
db.SaveChanges();
}
public void AddPartToBasket(int partID, int
quantity)
{
var basketLine =
db.BasketLines.FirstOrDefault(b =>
b.BasketID == BasketID && b.PartId
== partID);
if (basketLine == null)
{
basketLine = new BasketLine
{
PartId = partID,
BasketID = BasketID,
Quantity = quantity,
DateCreated = DateTime.Now
};
db.BasketLines.Add(basketLine);
}
else
{
basketLine.Quantity += quantity;
}
db.SaveChanges();
}
public void RemoveLine(int ID)
{
var basketLine = db.BasketLines.FirstOrDefault(b => b.BasketID == BasketID && b.ServiceID
== ID || b.PartId == ID);
if (basketLine != null)
{
db.BasketLines.Remove(basketLine);
}
db.SaveChanges();
}
public void UpdateBasket(List<BasketLine> lines)
{
foreach (var line in lines)
{
var basketLine = db.BasketLines.FirstOrDefault(b => b.BasketID == BasketID &&
b.ServiceID == line.ServiceID);
if (basketLine != null)
{
if (line.Quantity == 0)
{
RemoveLine(line.ServiceID);
}
else
{
basketLine.Quantity = line.Quantity;
}
}
}
db.SaveChanges();
}
public void EmptyBasket()
{
var basketLines = db.BasketLines.Where(b => b.BasketID == BasketID);
foreach (var basketLine in basketLines)
{
db.BasketLines.Remove(basketLine);
}
db.SaveChanges();
}
public List<BasketLine> GetBasketLines()
{
return db.BasketLines.Where(b => b.BasketID == BasketID).ToList();
}
public decimal GetTotalCost()
{
decimal basketTotal = decimal.Zero;
decimal serviceTotal = decimal.Zero;
decimal partTotal = decimal.Zero;
if (GetBasketLines().Count > 0)
{
serviceTotal = db.BasketLines.Where(b => b.BasketID == BasketID).Sum(b => b.Service.Price
* b.Quantity);
partTotal = db.BasketLines.Where(b => b.BasketID == BasketID).Sum(b => b.Part.Price
* b.Quantity);
basketTotal = serviceTotal + partTotal;
}
return basketTotal;
}
public int GetNumberOfItems()
{
int numberOfItems = 0;
if (GetBasketLines().Count > 0)
{
numberOfItems = db.BasketLines.Where(b => b.BasketID == BasketID).Sum(b => b.Quantity);
}
return numberOfItems;
}
public void MigrateBasket(string userName)
{
//find the current basket and store it in memory using ToList()
var basket = db.BasketLines.Where(b => b.BasketID == BasketID).ToList();
//find if the user already has a basket or not and store it in memory using ToList()
var usersBasket = db.BasketLines.Where(b => b.BasketID == userName).ToList();
//if the user has a basket then add the current items to it
if (usersBasket != null)
{
//set the basketID to the username
string prevID = BasketID;
BasketID = userName;
//add the lines in anonymous basket to the user's basket
foreach (var line in basket)
{
AddServiceToBasket(line.ServiceID, line.Quantity);
AddPartToBasket(line.PartId, line.Quantity);
}
//delete the lines in the anonymous basket from the database
BasketID = prevID;
EmptyBasket();
}
else
{
//if the user does not have a basket then just migrate this one
foreach (var basketLine in basket)
{
basketLine.BasketID = userName;
}
db.SaveChanges();
}
HttpContext.Current.Session[BasketSessionKey] = userName;
}
public decimal CreateOrderLines(int orderID)
{
decimal orderTotal = 0;
var basketLines = GetBasketLines();
foreach (var item in basketLines)
{
BillLine BillLine = new BillLine
{
Service = item.Service,
ServiceID = item.ServiceID,
ServiceName = item.Service.Name,
Quantity = item.Quantity,
ServicePrice = item.Service.Price,
BillID = orderID
};
orderTotal += (item.Quantity * item.Service.Price);
db.BillLines.Add(BillLine);
}
db.SaveChanges();
EmptyBasket();
return orderTotal;
}
}
BasketLine
public class BasketLine
{
public int ID { get; set; }
public string BasketID { get; set; }
public int ServiceID { get; set; }
public int PartId { get; set; }
[Range(0, 50, ErrorMessage = "Please enter a quantity between 0 and 50")]
public int Quantity { get; set; }
public DateTime DateCreated { get; set; }
public virtual Service Service { get; set; }
public virtual Part Part { get; set; }
}
Assumed that EF Code First is used, the exception message indicates that you're using foreign key constraint inside BasketLines table which references PartId primary key column in Parts table, and you're trying to insert a value into BasketLines.PartId column which not exist in Parts table at this statement:
basketLine = new BasketLine
{
PartId = partID, // this assignment is the problem source
BasketID = BasketID,
Quantity = quantity,
DateCreated = DateTime.Now
};
db.BasketLines.Add(basketLine);
Based from inspection, you're trying to build relationship between Service, Part and BasketLine entities, therefore I suggested to add ForeignKeyAttribute for ServiceId and PartId property in BasketLine entity:
public class BasketLine
{
public int ID { get; set; }
public string BasketID { get; set; }
[ForeignKey("Service")]
public int ServiceID { get; set; }
[ForeignKey("Part")]
public int PartId { get; set; }
[Range(0, 50, ErrorMessage = "Please enter a quantity between 0 and 50")]
public int Quantity { get; set; }
public DateTime DateCreated { get; set; }
public virtual Service Service { get; set; }
public virtual Part Part { get; set; }
}
Additionally, since it's stated that a BasketLine requires Service with optional Part, you may also try modify OnModelCreating() method inside DbContext like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BasketLine>()
.HasOptional(x => x.Part) // can save BasketLine without specifying Part
.WithRequired(x => x.Service); // cannot save BasketLine without using Service
}
Related issues:
Configure One-to-One Relationships in EF Code First
The INSERT statement conflicted with the FOREIGN KEY constraint
I am working with two different databases. I am passing the Model collection (SQL Server) to a ViewModel collection. The ViewModel has extra properties which I access out of a Visual Fox Pro database. I am able to map the existing properties, but the ViewModel does not save the data after passing the values to it.
The WoCust and the Lname fields return null, but the rest of the properties which come from the original Model pass to the properties in the ViewModel fine.
When I debug at the rdr for the OleDbCommand, it shows that the ViewModel is receiving a value for both rdr[WoCust] and rdr[Lname].
How do I make it so the ViewModel saves the new values?
WOSchedule.cs...
public partial class WOSchedule
{
public long Id { get; set; }
public string WoNo { get; set; }
public Nullable<long> QuoteTypeId { get; set; }
public Nullable<int> PriorityNo { get; set; }
public bool Active { get; set; }
public Nullable<System.DateTime> WoDate { get; set; }
public Nullable<long> QuoteID { get; set; }
public Nullable<System.DateTime> WoDone { get; set; }
public Nullable<long> WOScheduleListId { get; set; }
public string StorageLocation { get; set; }
public virtual QuoteType QuoteType { get; set; }
public virtual Quote Quote { get; set; }
public virtual WOScheduleList WOScheduleList { get; set; }
}
WoWcheduleVM.cs...
public partial class WoScheduleVM
{
public long Id { get; set; }
public string WoNo { get; set; }
public Nullable<long> QuoteTypeId { get; set; }
public Nullable<int> PriorityNo { get; set; }
public bool Active { get; set; }
public DateTime? WoDate { get; set; }
public Nullable<long> QuoteID { get; set; }
public DateTime? WoDone { get; set; }
public Nullable<long> WOScheduleListId { get; set; }
public string StorageLocation { get; set; }
public string WoCust { get; set; } // extra property
public string Lname { get; set; } // extra property
public virtual QuoteType QuoteType { get; set; }
public virtual Quote Quote { get; set; }
public virtual WOScheduleList WOScheduleList { get; set; }
}
WOSchedulesController.cs
string cs = ConfigurationManager.ConnectionStrings["foxproTables"].ConnectionString;
OleDbConnection cn = new OleDbConnection(cs);
var wOSchedules = db.WOSchedules.Where(w => w.WoDone == null).Include(w => w.QuoteType);
var wOSchedulesVM = wOSchedules.Select(s => new ViewModels.WoScheduleVM()
{
Id = s.Id,
WoNo = s.WoNo,
QuoteTypeId = s.QuoteTypeId,
PriorityNo = s.PriorityNo,
Active = s.Active,
WoDate = s.WoDate,
QuoteID = s.QuoteID,
WoDone = s.WoDone,
WOScheduleListId = s.WOScheduleListId,
StorageLocation = s.StorageLocation
});
cn.Open();
foreach (var sch in wOSchedulesVM)
{
string conn = #"SELECT wo_cust, lname FROM womast INNER JOIN custmast ON womast.wo_cust = custmast.cust_id WHERE wo_no = '" + sch.WoNo + "'";
OleDbCommand cmdWO = new OleDbCommand(conn, cn);
OleDbDataReader rdr = cmdWO.ExecuteReader();
while (rdr.Read())
{
sch.WoCust = ((string)rdr["wo_cust"]).Trim();
sch.Lname = ((string)rdr["lname"]).Trim();
}
}
cn.Close();
return View(wOSchedulesVM.OrderByDescending(d => d.WoDate).ToList());
The problem is you're using foreach loop for iterating wOSchedulesVM collection, which renders the source collection immutable during iteration. The older documentation version explicitly explains that behavior:
The foreach statement is used to iterate through the collection to get
the information that you want, but can not be used to add or remove
items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.
Therefore, you should use for loop to be able to modify property values inside that collection, as shown in example below:
using (var OleDbConnection cn = new OleDbConnection(cs))
{
cn.Open();
string cmd = #"SELECT wo_cust, lname FROM womast INNER JOIN custmast ON womast.wo_cust = custmast.cust_id WHERE wo_no = #WoNo";
// not sure if it's 'Count' property or 'Count()' method, depending on collection type
for (int i = 0; i < wOSchedulesVM.Count; i++)
{
var sch = wOSchedulesVM[i];
using (OleDbCommand cmdWO = new OleDbCommand(conn, cn))
{
cmd.Parameters.AddWithValue("#WoNo", sch.WoNo)
OleDbDataReader rdr = cmdWO.ExecuteReader();
if (rdr.HasRows)
{
while (rdr.Read())
{
sch.WoCust = (!rdr.IsDbNull(0)) ? rdr.GetString(0).Trim() : string.Empty;
sch.Lname = (!rdr.IsDbNull(1)) ? rdr.GetString(1).Trim() : string.Empty;
}
}
}
}
}
Note: This example includes 3 additional aspects, i.e. parameterized query, checking row existence with HasRows property and checking against DBNull.Value with IsDbNull().
Related issue: What is the best way to modify a list in a 'foreach' loop?
I want to ignore some properties from my Object during run time. The properties are decorated with data member attribute (Without data member attribute excludepropertyreferencces is working fine). Can you please provide some insight? Thanks
Question : HOW TO EXCLUDE PROPERTIES AT RUN TIME, IF THEY ARE DECORATE WITH DATAMEMBER ATTRIBUTE ?
ServiceStack , ExcludePropertyReferences
var model = new Model {
FirstName = "First Name",
LastName = "Last Name",
Children = new List<ModelChild>{
new ModelChild { ChildFirstName = "ChildFirstName 1", ChildLastName = "ChildLastName 1" },
new ModelChild { ChildFirstName = "ChildFirstName 2", ChildLastName = "ChildLastName 2" }
}
};
var model1 = new Model1 {
FirstName = "First Name",
LastName = "Last Name",
Children = new List<Model1Child>{
new Model1Child { ChildFirstName = "ChildFirstName 1", ChildLastName = "ChildLastName 1" },
new Model1Child { ChildFirstName = "ChildFirstName 2", ChildLastName = "ChildLastName 2" }
}
};
Console.WriteLine("Properties won't get ignored because the Model is decorated with Serialization Attributes");
using(MemoryStream stream = new MemoryStream())
using (var jsConfig = JsConfig.BeginScope()) {
jsConfig.ExcludeTypeInfo = true;
jsConfig.ExcludePropertyReferences = new [] { "Model.LastName", "ModelChild.ChildLastName" }.ToArray();
JsonSerializer.SerializeToStream(model, model.GetType(), stream);
LINQPad.Extensions.Dump(System.Text.Encoding.Default.GetString(stream.ToArray()));
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Properties will get ignored because the Model is not decorated with Serialization Attributes");
using(MemoryStream stream = new MemoryStream())
using (var jsConfig = JsConfig.BeginScope()) {
jsConfig.ExcludeTypeInfo = true;
jsConfig.ExcludePropertyReferences = new [] { "Model1.LastName", "Model1Child.ChildLastName" }.ToArray();
JsonSerializer.SerializeToStream(model1, model1.GetType(), stream);
LINQPad.Extensions.Dump(System.Text.Encoding.Default.GetString(stream.ToArray()));
}
// Define other methods and classes here
[DataContract()]
public class Model {
[DataMember(Name = "first_name",EmitDefaultValue = false )]
public string FirstName { get; set; }
[DataMember(Name = "last_name")]
public string LastName { get; set; }
[DataMember(Name = "collections")]
public List<ModelChild> Children { get; set; }
}
[DataContract()]
public class ModelChild {
[DataMember(Name = "child_first_name")]
public string ChildFirstName { get; set; }
[DataMember(Name = "child_last_name")]
public string ChildLastName { get; set; }
}
public class Model1 {
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Model1Child> Children { get; set; }
}
public class Model1Child {
public string ChildFirstName { get; set; }
public string ChildLastName { get; set; }
}
Consider the following domain model:
class Customer
{
int id {get;set}
string Name {get;set}
List<Contact> Contacts {get;set}
}
class Contact
{
int id {get;set}
string FullName {get;set}
List<PhoneNumber> {get;set}
}
class PhoneNumber
{
int id {get;set}
PhoneType Type {get;set}
string Number {get;set}
}
Customer, Contact & PhoneNumbers are separate entities in our DB.
Any Suggestions as to how to populate a full customer with all its linked contacts and the contacts phone numbers in the most efficient way using ORMLite and/or Dapper, with consideration of calls to the db and cpu cycles to map to the POCOs?
If you're using ORMLite, then it has an attribute called [Ignore] which you can place on your properties, which means they won't be persisted (I find this useful for having navigation properties on my models)
So with that, I would do the following:
public class Customer : IHasIntId
{
[AutoIncrement]
[PrimaryKey]
public int Id {get;set}
public string Name {get;set}
[Ignore]
public List<Contact> Contacts {get;set}
}
public class Contact : IHasIntId
{
[AutoIncrement]
[PrimaryKey]
public int Id {get;set}
public string FullName {get;set}
[References(typeof(Customer)]
public int CustomerId {get;set;}
[Ignore]
public List<PhoneNumber> PhoneNumbers {get;set}
}
public class PhoneNumber : IHasIntId
{
[AutoIncrement]
[PrimaryKey]
public int id {get;set}
public PhoneType Type {get;set}
public string Number {get;set}
[References(typeof(Contact))]
public int ContactId {get;set;}
}
Then in your CustomersService, something like this should suffice:
public class CustomersService : Service {
public object Get(Customers request) {
var customers = Db.Select<Customer>();
var customerIds = customers.Select(c=>c.Id).ToList();
var contacts = Db.Select<Contact>(c=>customerIds.Contains(c.CustomerId));
var contactIds = contacts.Select(c=>c.Id).ToList();
var phoneNumbers = Db.Select<PhoneNumber>(p=>contactIds.Contains(p.ContactId));
customers.ForEach(customer=> {
var customerContacts = contacts.Where(c=>c.CustomerId == customer.Id);
customerContacts.ForEach(contact=> {
contact.PhoneNumbers = phoneNumbers.Where(p=>p.ContactId ==
contact.Id).ToList();
});
customer.Contacts = customerContacts
});
return new CustomersResponse {
Result = customers
}
}
}
Note: I'm using ServiceStack ORMLite version 3.*. I understand in v4 there is a better way of doing this, check here: https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/LoadReferencesTests.cs#L80
I have updated my example to pull the flat customer list and transform it into a hierarchy. A couple of things to note:
I'm explicitly specifying columns in the join builder because the Id columns have the same name in the join
the performance should be decent because I'm using a hash for customers and adding phone numbers, so the only real loop is the search for a matching contact
Models:
class CustomerComplete
{
[BelongTo(typeof(Customer))]
public string CustomerName { get; set; }
[BelongTo(typeof(Customer))]
public int CustomerId { get; set; }
[BelongTo(typeof(Contact))]
public int ContactId { get; set; }
[BelongTo(typeof(Contact))]
public string ContactFullName { get; set; }
[BelongTo(typeof(PhoneNumber))]
public int PhoneNumberId { get; set; }
[BelongTo(typeof(PhoneNumber))]
public PhoneType Type { get; set; }
[BelongTo(typeof(PhoneNumber))]
public string Number { get; set; }
}
class Customer
{
public Customer()
{
this.Contacts = new List<Contact>();
}
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
[Ignore]
public List<Contact> Contacts { get; set; }
}
class Contact
{
public Contact()
{
this.PhoneNumbers = new List<PhoneNumber>();
}
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
public string FullName { get; set; }
[References(typeof(Customer))]
public int CustomerId { get; set; }
[Ignore]
public List<PhoneNumber> PhoneNumbers { get; set; }
}
class PhoneNumber
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
public PhoneType Type { get; set; }
public string Number { get; set; }
[References(typeof(Contact))]
public int ContactId { get; set; }
}
enum PhoneType { None = 0 }
Usage:
db.CreateTableIfNotExists<Customer>();
db.CreateTableIfNotExists<Contact>();
db.CreateTableIfNotExists<PhoneNumber>();
db.Insert<Customer>(new Customer { Name = "Widget Co Inc" });
var customerId = (int) db.GetLastInsertId();
db.Insert<Contact>(new Contact { FullName = "John Smith", CustomerId = customerId });
var contactId = (int)db.GetLastInsertId();
db.Insert<PhoneNumber>(new PhoneNumber { ContactId = contactId, Number = "555.555.5555", Type = PhoneType.None });
db.Insert<PhoneNumber>(new PhoneNumber { ContactId = contactId, Number = "444.444.4444", Type = PhoneType.None });
db.Insert<Contact>(new Contact { FullName = "Jack Smith", CustomerId = customerId });
contactId = (int)db.GetLastInsertId();
db.Insert<PhoneNumber>(new PhoneNumber { ContactId = contactId, Number = "111.111.1111", Type = PhoneType.None });
// contact without phone number test
db.Insert<Contact>(new Contact { FullName = "Jill Smith", CustomerId = customerId });
// build join
var jn = new JoinSqlBuilder<Customer, Customer>()
.LeftJoin<Customer, Contact>(
customer => customer.Id,
contact => contact.CustomerId,
customer => new { CustomerId = customer.Id, CustomerName = customer.Name },
contact => new { ContactId = contact.Id, ContactFullName = contact.FullName })
.LeftJoin<Contact, PhoneNumber>(
contact => contact.Id,
phone => phone.ContactId,
null,
phone => new { PhoneNumberId = phone.Id, phone.Number, phone.Type });
var all = db.Select<CustomerComplete>(jn.ToSql());
var customerHash = new Dictionary<int, Customer>();
foreach (var cc in all)
{
if (!customerHash.ContainsKey(customerId))
customerHash[customerId] = new Customer { Id = cc.CustomerId, Name = cc.CustomerName };
if (cc.ContactId == 0) continue; // no contacts for this customer
var contact = customerHash[customerId].Contacts.Where(x => x.Id == cc.ContactId).FirstOrDefault();
if (null == contact)
{
contact = new Contact
{
CustomerId = customerId,
Id = cc.ContactId,
FullName = cc.ContactFullName
};
// add contact
customerHash[customerId].Contacts.Add(contact);
}
if (cc.PhoneNumberId == 0) continue; // no phone numbers for this contact
contact.PhoneNumbers.Add(new PhoneNumber
{
ContactId = cc.ContactId,
Id = cc.PhoneNumberId,
Number = cc.Number,
Type = cc.Type
});
}
// our hierarchical customer list
var customers = customerHash.ToList();
Also note that v4 seems to support a much easier way of doing this using the Reference attribute, though it may make multiple calls to the DB. See the unit tests here for an example.