问题
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?
回答1:
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
回答2:
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.
来源:https://stackoverflow.com/questions/20563669/populating-pocos-with-servicestack-ormlite