I was wondering if we want to check for adding new item if projectId exist in db, shell we somehow in AddItem method to insert Guard.Agains.NotFound(???) or not?
I'm asking because if create some entity:
public class Country : BaseEntity<int>, IAggregateRoot
{
public string Name { get; private set; }
private readonly List<District> _districts = new List<District>();
public IEnumerable<District> Districts => _districts.AsReadOnly();
public Country(string name)
{
Name = Guard.Against.NullOrEmpty(name, nameof(name));
}
public void AddDistrict(District newDistrict)
{
Guard.Against.Null(newDistrict, nameof(newDistrict));
Guard.Against.NegativeOrZero(newDistrict.CountryId, nameof(newDistrict.CountryId));
_districts.Add(newDistrict);
}
}
public class District : BaseEntity<int>, IAggregateRoot
{
public string Name { get; set; }
public int CountryId { get; set; }
public List<Municipality> Municipalities { get; set; }
}
How we can validate if countryId sent by request exist in DB ?
Example if create integration test like :
[Fact]
public async Task AddDistrict()
{
var districtName = "District";
var countryRepository = GetCountryRepository();
var country = new Country("Country");
await countryRepository.AddAsync(country);
var district = new District
{
CountryId = 2,
Name = districtName
};
country.AddDistrict(district);
await countryRepository.UpdateAsync(country);
Assert.Equal(1, district.Id);
}
No matter with intenger value I put as CountryId test will pass till is not 0 or negative integer, but I want to check if that id of country entity exist in DB. Where would be best place to manage this check ?
Regards,
Simplest way is to request a Country object be provided to the constructor of District:
public class District
{
public string Name { get; private set; }
public int CountryId { get; private set; }
public District(string name, Country country)
{
if (country == null)
throw new Exception("Missing country.");
Name = name;
CountryId = country.Id
}
}
Now you've force the client of the domain to provide a Country. If the client (application layer) cannot retrieve a valid Country from the Country repository based on the provided id then your constructur will throw upon getting a null country.
Alternatively, keep the CountryId as a constructor parameter on District, make the District constructor internal so that it cannot be created outside of the domain and then make the Country object the factory for District:
public class Country
{
public District CreateDistrict(string name)
{
return new District(name, this.Id);
}
}
This will also force client to get a concrete Country before asking it to create the District.
Related
In a clean architecture project the domain layer contains: DTO interfaces, Events, Factories, Models, Exceptions, etc...
Every domain object contains a constructor with arguments through which data is passed.
I am using factories which accept a DTO interface from which domain objects are created.
The data models in the infrastructure layer implement the DTO interfaces in the domain layer.
DTO:
namespace Acme.Core.Domain.Identity.DTO
{
public interface IBuyerDto : IPersonDto
{
IAddressDto BillingAddress { get; set; }
IAddressDto ShippingAddress { get; set; }
}
}
Domain Models:
namespace Acme.Core.Domain.Identity.Models.BuyerAggregate
{
public sealed class Buyer : Aggregate<Buyer, BuyerId>, IPerson
{
public Buyer(BuyerId id, PersonName name, DateOfBirth dateOfBirth, Gender gender, string pictureUrl, Address billingAddress, Address shippingAddress, Account account) : base(id)
{
Name = name;
DateOfBirth = dateOfBirth;
Gender = gender;
BillingAddress = billingAddress;
ShippingAddress = shippingAddress;
Account = Guard.Against.Null(account, nameof(account));
PictureUrl = pictureUrl;
}
public Account Account { get; private set; }
public PersonName Name { get; private set; }
public DateOfBirth DateOfBirth { get; private set; }
public string PictureUrl { get; private set; }
public Gender Gender { get; private set; }
public Address BillingAddress { get; private set; }
public Address ShippingAddress { get; private set; }
public void UpdateName(PersonName personName)
{
Name = personName;
}
public void UpdateBillingAddress(Address billingAddress)
{
BillingAddress = billingAddress;
}
public void UpdateShippingAddress(Address shippingAddress)
{
ShippingAddress = shippingAddress;
}
}
}
namespace Acme.Core.Domain.Identity.Models
{
public class Account : Entity<Account, AccountId>
{
public Account(AccountId id, string userName, string normalizedUserName, string passwordHash, string concurrencyStamp, string securityStamp, string email, string normalizedEmail, bool emailConfirmed, string phoneNumber, bool phoneNumberConfirmed, bool twoFactorEnabled, DateTimeOffset? lockoutEnd, bool lockoutEnabled, int accessFailedCount, AccountStatus status, List<RoleId> roles, List<AccountClaim> accountClaims, List<AccountLogin> accountLogins, List<AccountToken> accountTokens) : base(id)
{
UserName = Guard.Against.NullOrWhiteSpace(userName, nameof(userName));
NormalizedUserName = Guard.Against.NullOrWhiteSpace(normalizedUserName, nameof(normalizedUserName));
PasswordHash = Guard.Against.NullOrWhiteSpace(passwordHash, nameof(passwordHash));
ConcurrencyStamp = concurrencyStamp;
SecurityStamp = securityStamp;
Email = Guard.Against.NullOrWhiteSpace(email, nameof(email));
NormalizedEmail = Guard.Against.NullOrWhiteSpace(normalizedEmail, nameof(normalizedEmail));
EmailConfirmed = emailConfirmed;
PhoneNumber = phoneNumber;
PhoneNumberConfirmed = phoneNumberConfirmed;
TwoFactorEnabled = twoFactorEnabled;
LockoutEnd = lockoutEnd;
LockoutEnabled = lockoutEnabled;
AccessFailedCount = accessFailedCount;
Status = Guard.Against.Null(status, nameof(status));
_roles = Guard.Against.Null(roles, nameof(roles));
_accountClaims = accountClaims;
_accountLogins = accountLogins;
_accountTokens = accountTokens;
}
public string UserName { get; private set; }
public string NormalizedUserName { get; private set; }
public string PasswordHash { get; private set; }
public string ConcurrencyStamp { get; private set; }
public string SecurityStamp { get; private set; }
public string Email { get; private set; }
public string NormalizedEmail { get; private set; }
public bool EmailConfirmed { get; private set; }
public string PhoneNumber { get; private set; }
public bool PhoneNumberConfirmed { get; private set; }
public bool TwoFactorEnabled { get; private set; }
public DateTimeOffset? LockoutEnd { get; private set; }
public bool LockoutEnabled { get; private set; }
public int AccessFailedCount { get; private set; }
public AccountStatus Status { get; private set; }
private List<RoleId> _roles;
public IReadOnlyCollection<RoleId> Roles
{
get
{
return _roles;
}
}
private List<AccountClaim> _accountClaims;
public IReadOnlyCollection<AccountClaim> AccountClaims
{
get
{
return _accountClaims;
}
}
private List<AccountLogin> _accountLogins;
public IReadOnlyCollection<AccountLogin> AccountLogins
{
get
{
return _accountLogins;
}
}
private List<AccountToken> _accountTokens;
public IReadOnlyCollection<AccountToken> AccountTokens
{
get
{
return _accountTokens;
}
}
public void AddRole(long roleId)
{
var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();
if (role == null)
{
_roles.Add(new RoleId(roleId));
}
}
public void RemoveRole(long roleId)
{
var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();
if (role == null)
{
_roles.Remove(role);
}
}
public void ActivateAccount()
{
Status = AccountStatus.Active;
}
public void BanAccount()
{
Status = AccountStatus.Banned;
}
public void CloseAccount()
{
Status = AccountStatus.Closed;
}
public void LockAccount()
{
Status = AccountStatus.Locked;
}
public void NewAccount()
{
Status = AccountStatus.New;
}
}
}
Factories:
namespace Acme.Core.Domain.Identity.Factories
{
public class BuyerAggregateFatory : IBuyerAggregateFactory
{
private readonly IPersonNameFactory _personNameFactory;
private readonly IDateOfBirthFactory _dateOfBirthFactory;
private readonly IGenderFactory _genderFactory;
private readonly IAccountFactory _accountFactory;
private readonly IAddressFactory _addressFactory;
public BuyerAggregateFatory(IPersonNameFactory personNameFactory,
IDateOfBirthFactory dateOfBirthFactory,
IGenderFactory genderFactory,
IAccountFactory accountFactory,
IAddressFactory addressFactory)
{
_personNameFactory = Guard.Against.Null(personNameFactory);
_dateOfBirthFactory = Guard.Against.Null(dateOfBirthFactory);
_genderFactory = Guard.Against.Null(genderFactory);
_accountFactory = Guard.Against.Null(accountFactory);
_addressFactory = Guard.Against.Null(addressFactory);
}
public Buyer Create(IBuyerDto dto)
{
BuyerId aggregateId = new BuyerId(dto.Id);
PersonName name = _personNameFactory.Create(dto.Name);
DateOfBirth dob = _dateOfBirthFactory.Create(dto.DateOfBirth);
Gender gender = _genderFactory.Create(dto.GenderId);
Address billingAddress = _addressFactory.Create(dto.BillingAddress);
Address shippingAddress = _addressFactory.Create(dto.ShippingAddress);
Account account = _accountFactory.Create(dto.Account);
return new Buyer(aggregateId, name, dob, gender, dto.PictureUrl, billingAddress, shippingAddress, account);
}
}
}
From the application layer a service class does the orchestration for the use case, using the repository interface and factory interface.
Use case 1: During an update operation I fetch existing data of the aggregate, from the database using a repository. I need to update one or two properties of a domain aggregate root object. Example: I need to update billing address or shipping address.
Use case 2: During an update operation, I fetch existing data of the aggregate, from the database using a repository. I need to update the account status. I am calling the status update method from domain aggregate root object. Example: buyerAggregate.Account.ActivateAccount()
Am i updating the domain aggregate root object and its properties in right way?
In use case 2, your aggregate would be the Account, not the Buyer. There's no need for the Buyer to be involved in the transaction.
So, for this case, you would retrieve Account from the repository and then call ActivateAccount() directly.
Any aggregate that you have designed for a use case should provide the full interface for making changes to the aggregate. In other words, your application layer will only work with properties and methods on the aggregate root. If a child entity needs changing that method should be implemented on your aggregate root. You should not directly interact with child properties of an aggregate. It is the aggregate's responsibility to avoid any invariants within its scope. If you change a child object directly, you may put the whole aggregate in an invalid state because the aggregate was not able to enforce controls.
So I'm creating a simple MVC app that uses the Absence class as a model which holds different properties including an object from the Employee class-another model that holds various properties:
public class Absence
{
public int Id { get; set; }
public string Reason { get; set; }
public int Day { get; set; }
public int Month { get; set; }
public bool isApproved { get; set; }
public int employee_Id { get; set; }
public virtual Employee employee { get; set; }
public Absence()
{
employee = new Employee();
}
}
And I created a controller that has an ActionResult function for the create View:
[HttpGet]
public ActionResult Create()
{
Absence abs = new Absence();
return View(abs);
}
[HttpPost]
public ActionResult Create(Absence abb)
{
Employee emp = database.Employees.FirstOrDefault(z => z.Id == abb.employee_Id);
System.Diagnostics.Debug.WriteLine(emp.Name);
abb.employee.Name = emp.Name;
abb.employee.Surname = emp.Surname;
System.Diagnostics.Debug.WriteLine(abb.employee.Name);
database.Absences.Add(abb);
database.SaveChanges();
return Redirect("/Absence");
}
The idea is to talk to the database find an Employee object with the same EmployeeId and set the name and surname of the employee object of the abb object to be the same and after testing it with the debugger I can see that it works.
However when I want to display all the added absences including the name and surname of their employee like this:
public ActionResult Index()
{
return View(database.Absences.ToList());
}
The name and surname of all the employees don't show.
It seems that all the properties are saved in the database using entity framework except for the Employee object.
Any ideas for how to save it?
Can you try saving the Absences first before updating the employee details? I don't exactly know how your database models are configured and this is just an assumption. The absence that is referencing your employee is not yet available when you are supplying the employee details.
Employee emp = database.Employees.FirstOrDefault(z => z.Id == abb.employee_Id);
database.Absences.Add(abb); //Moved adding here
database.SaveChanges(); // Perform the saving
abb.employee.Name = emp.Name;
abb.employee.Surname = emp.Surname;
database.SaveChanges();
I'm trying to create an attribute that can validate a complex type both on the server and client side. This attribute will be used for required and non required complex types such as the following Address Class
public partial class AddressViewModel
{
[DisplayName("Address 1")]
[MaxLength(100)]
public virtual string Address1 { get; set; }
[DisplayName("Address 2")]
[MaxLength(100)]
public virtual string Address2 { get; set; }
[MaxLength(100)]
public virtual string City { get; set; }
[MaxLength(50)]
public virtual string State { get; set; }
[MaxLength(10)]
[DisplayName("Postal Code")]
public virtual string PostalCode { get; set; }
[MaxLength(2)]
public virtual string Country { get; set; }
}
The problem is that this model could be required sometimes and optional other times. I know that I could simply create another RequiredAddressViewModel class that has the Required attribute associated with the properties I deem required. I feel like there could be a reusable solution, such as a ValidationAttribute.
I created the following classes and they work server side, but do not work for client side.
public class AddressIfAttribute : ValidationAttribute, IClientValidatable
{
public string Address1 { get; private set; }
public string Address2 { get; private set; }
public string City { get; private set; }
public string State { get; private set; }
public string PostalCode { get; private set; }
public string Country { get; private set; }
public bool IsRequired { get; private set; }
public AddressIfAttribute(bool isRequired) : base("The field {0} is required.")
{
IsRequired = isRequired;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var address = value as AddressViewModel;
Address1 = address.Address1;
Address2 = address.Address2;
City = address.City;
State = address.State;
Country = address.Country;
PostalCode = address.PostalCode;
var results = new List<ValidationResult>();
var context = new ValidationContext(address, null, null);
Validator.TryValidateObject(address, context, results, true);
if (results.Count == 0 && IsRequired)
{
if (string.IsNullOrEmpty(Address2))
return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));
}
else if (results.Count != 0)
{
var compositeResults = new CompositeValidationResult(string.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[]
{
new ModelClientValidationAddressIfRule(string.Format(ErrorMessageString,metadata.GetDisplayName()), Address1, Address2, City, State, Country, PostalCode,IsRequired)
};
}
}
public class ModelClientValidationAddressIfRule : ModelClientValidationRule
{
public ModelClientValidationAddressIfRule(string errorMessage, object address1, object address2, object city, object state, object country, object postalCode, bool isRequired)
{
ErrorMessage = errorMessage;
ValidationType = "addressif";
ValidationParameters.Add("address1", address1);
ValidationParameters.Add("address2", address2);
ValidationParameters.Add("city", city);
ValidationParameters.Add("state", state);
ValidationParameters.Add("country", country);
ValidationParameters.Add("postalCode", postalCode);
ValidationParameters.Add("isrequired", isRequired.ToString().ToLower());
}
Since the AddressIf attribute is on a complex type the necessary markup isn't added and unobtrusive javascript doesn't validate these fields.
So if I want the rendered HTML to have the proper data-* fields, is my only solution to create another RequiredAddressViewModel? At this point, it might be the easiest.
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.
Using WCF Data Services 4.0, I cannot get the hierarchical data to return. I have a class Employee that has a collection of EquipmentIds. Those EquipmentIds are getting lost over the wire. Here's my code:
public class ODataV2 : DataService<ODataV2Model>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
public class ODataV2Model
{
public ODataV2Model()
{
Employees = new List<Employee>{
new Employee { Id = 1, Name="Doug", EquipmentIds = new List<Equipment> { new Equipment { Id = 1 },new Equipment { Id = 2 } }.AsQueryable()},
new Employee { Id = 2, Name= "George", EquipmentIds = new List<Equipment> {new Equipment { Id = 3}, new Equipment { Id = 5} }.AsQueryable() }
}.AsQueryable();
}
public IQueryable<Employee> Employees { get; private set; }
public IQueryable<Equipment> EquipmentIds { get; private set; }
}
[DataServiceKey("Id")]
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public IQueryable<Equipment> EquipmentIds { get; set; }
}
[DataServiceKey("Id")]
public class Equipment
{
public int Id { get; set; }
}
When I run LinqPad on it, I get this:
I should have both collections of EquipmentIds with counts of 2, but I have 0. I don't receive an error, but the data never makes it to the client.
I switched to WCF Data Services v 5.0 and it works successfully on the .NET side, but I lose the ability to query with LinqPad. Is there a way for this to work in v 4.0?
If not, is there a way to upgrade LinqPad to recognize odata v3 (WCF Data Services 5.0)?
The query ~/Employees will only include the Employee entities and not any navigation property content. This is to reduce the payload size. If you really want to include some of the navigation properties just specify for example ~/Employees?$expand=EquipmentIds.