EditCurrent Limitations
There is one noticeable limitations that Nvigorate currently has in the Alpha version when it comes to domain model persistence:
- You're expected to either have properties for your foreign keys or use back references
The other significant limitation Nvigorate has which is inherited from PostSharp is that in order to get full ORM features, your domain model has to have the class StateTracking at it's root OR implement the ITrackState interface.
If any of this is unclear, don't worry, you'll see the implications in the domain model.
Disclaimer
Please keep in mind that I'm primarily just trying to demonstrate Nvigorate's ORM features and not how you should design a domain model
EditThe Demo Domain
This domain model will be based on a (lame) customer database for a mechanic's. Our domain objects will be Person, Address, Vehicle, Appointment, Bill and LineItem. Each class will inherit from a base class called DemoBase that will have the property for the record's Id (primary key).
- A person has one or more vehicles
- A vehicle has one or more drivers
- A person lives at an address
- A person has different phone numbers (home, cell, work)
- A person makes an appointment for a vehicle OR
- An appointment is for a single person and one of their vehicles
- A completed appointment has a bill
- A bill has one or more line items
- A line item is either for a product consumed or a service performed
The reason for those statements is to explain the relationships as you'll see them in the domain model. This model is not a complete representation of the source code for the domain but instead is intended to provide the basis for the conceptual model. The demo will be made available (once it's complete).
EditDemoBase
public class DemoBase : StateTracking
{
public long Id { get; private set; }
}
EditPerson
[StateTrackingAspect]
public class Customer : DemoBase
{
public string FirstName { get; set; }
public string LastName { get; set; }
public PhoneNumber PrimaryPhone { get; set; }
public PhoneNumber SecondaryPhone { get; set; }
public EmailAddress PrimaryEmail { get; set; }
public EmailAddress SecondaryEmail { get; set; }
public Address MainAddress { get; set; }
public IList<IVehicle> Vehicles { get; set; }
public Customer()
{
Vehicles = DomainFactory.Get<IList<IVehicle>>();
}
}
EditContact Information
public struct PhoneNumber
{
private bool _valid;
private string _country;
private string _area;
private string _exchange;
private string _number;
private string _extension;
public string Country
{
get { return _country; }
private set { _country = value; }
}
public string Area
{
get { return _area; }
private set { _area = value; }
}
public string Exchange
{
get { return _exchange; }
private set { _exchange = value; }
}
public string Number
{
get { return _number; }
private set { _number = value; }
}
public string Extension
{
get { return _extension; }
private set { _extension = value; }
}
public PhoneNumber(string country, string area, string exchange, string number, string extension)
{
_country = country;
_area = area;
_exchange = exchange;
_number = number;
_extension = extension;
_valid = true;
}
public override string ToString()
{
if (!_valid)
return "";
var format = "{0}-{1}-{2}";
var sections = new List<string>
{
Area,
Exchange,
Number
};
if(!string.IsNullOrEmpty(Country))
{
sections.Add(Country);
format = string.Concat("{3}-", format);
}
if(!string.IsNullOrEmpty(Extension))
{
sections.Add(Extension);
format = string.Concat(format, sections.Count == 3 ? " ext. {3}" : " ext. {4}");
}
return string.Format(format, sections);
}
public override bool Equals(object obj)
{
if(!_valid)
return false;
var candidate = (PhoneNumber) obj;
return
candidate.Country.Equals(Country) &&
candidate.Country.Equals(Area) &&
candidate.Country.Equals(Exchange) &&
candidate.Country.Equals(Number) &&
candidate.Country.Equals(Extension);
}
}
public struct EmailAddress
{
private bool _valid;
private string _account;
private string _domainName;
public string Account
{
get { return _account; }
private set { _account = value; }
}
public string Domain
{
get { return _domainName; }
private set { _domainName = value; }
}
public EmailAddress(string emailAddress)
{
var parts = emailAddress.Split('@');
_account = parts[0];
_domainName = parts[1];
_valid = true;
}
public override string ToString()
{
return string.Format("{0}@{1}", _account, _domainName);
}
public override bool Equals(object obj)
{
if(!_valid)
return false;
var candidate = (EmailAddress) obj;
return
candidate.Account.Equals(Account) &&
candidate.Domain.Equals(Domain);
}
}
EditIVehicle
public interface IVehicle
{
IList<Customer> Drivers { get; set; }
string Make { get; set; }
string Model { get; set; }
int Year { get; set; }
string Color { get; set; }
}
public enum VehicleType
{
Car = 1,
Truck = 2
}
public abstract class Vehicle : DemoBase, IVehicle
{
public virtual IList<Customer> Drivers { get; set; }
public virtual string Make { get; set; }
public virtual string Model { get; set; }
public virtual int Year { get; set; }
public virtual string Color { get; set; }
public virtual VehicleType VehicleType { get; set; }
public Vehicle()
{
Drivers = DomainFactory.Get <IList<Customer>>();
}
}
[StateTrackingAspect]
public class Car : Vehicle
{
}
[StateTrackingAspect]
public class Truck : Vehicle
{
}
EditAddress
[StateTrackingAspect]
public class Address : DemoBase
{
public List<Customer> Residents { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
EditAppointment
[StateTrackingAspect]
public class Appointment : DemoBase
{
public Customer Customer { get; set; }
public IVehicle Vehicle { get; set; }
public Bill Bill { get; set; }
public DateTime Time { get; set; }
}
EditBill
[StateTrackingAspect]
public class Bill : DemoBase
{
public IList<LineItem> LineItems { get; set; }
public Appointment Appointment { get; set; }
public Bill()
{
LineItems = DomainFactory.Get<IList<LineItem>>();
}
}
EditLineItem
public abstract class LineItem : DemoBase
{
public Bill Bill { get; set; }
public decimal Units { get; set; }
}
public class ServiceLineItem : LineItem
{
public Service ServicePerformed { get; set; }
}
public class ProductLineItem : LineItem
{
public Product ProductUsed { get; set; }
}
public abstract class HasCostBase : DemoBase
{
public string Name { get; set; }
public decimal Cost { get; set; }
}
[StateTrackingAspect]
public class Service : HasCostBase
{
}
[StateTrackingAspect]
public class Product : HasCostBase
{
}
It's time to see how to map these to the schema using the
Fluent Mapping API.