Populating POCO's with ServiceStack.OrmLite

▼魔方 西西 提交于 2019-12-07 23:51:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!