Say I have a value object class, FullName, that is used as a property in an Employee entity class. The FullName may have a middle initial, nick name, etc; but from a domain perspective I would like to only enforce that both the FirstName and LastName properties of the FullName are valued.
I want to express this as part of an EmployeeValidator : ValidationDef{Employee} object, as opposed to an attribute.
Do I first need to make a class validator for FullName (ie, FirstAndLAstNameRequired) and then say that the FullName property in Employee is Valid (using some loquacious form of the ValidAttribute)?
As an aside, it seems that this documentation is still the best out there, but it does look dated at three years old. Is there anything newer that I missed?
Cheers,
Berryl
UPDATE
I haven't figured this out yet, but I have found what is likely the best source of NHib Validator info here: http://fabiomaulo.blogspot.com/search/label/Validator
Here is some psuedo code to express the question better too:
/// <summary>A person's name.</summary>
public class FullName
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MiddleName { get; set; }
public virtual string NickName { get; set; }
}
public class EmployeeValidator : ValidationDef<Employee>
{
public EmployeeValidator()
{
Define(x => x.FullName).FirstAndLastNameRequired(); // how to get here!!
}
}
UPDATE FOR DAVID
public class FullNameValidator : ValidationDef<FullName>
{
public FullNameValidator() {
Define(n => n.FirstName).NotNullable().And.NotEmpty().And.MaxLength(25);
Define(n => n.LastName).NotNullable().And.NotEmpty().And.MaxLength(35);
// not really necessary but cool that you can do this
ValidateInstance
.By(
(name, context) => !name.FirstName.IsNullOrEmptyAfterTrim() && !name.LastName.IsNullOrEmptyAfterTrim())
.WithMessage("Both a First and Last Name are required");
}
}
public class EmployeeValidator : ValidationDef<Employee>
{
public EmployeeValidator()
{
Define(x => x.FullName).IsValid(); // *** doesn't compile !!!
}
}
To get the FullName validated when you validate the employee, I think you'd do something like the following:
public class EmployeeValidator : ValidationDef<Employee>
{
public EmployeeValidator()
{
Define(x => x.FullName).IsValid();
Define(x => x.FullName).NotNullable(); // Not sure if you need this
}
}
Then the FullName Validator would just be something like:
public class FullNameValidator : ValidationDef<FullName>
{
public EmployeeValidator()
{
Define(x => x.FirstName).NotNullable();
Define(x => x.LastName).NotNullable();
}
}
Alternatively I think you could do something like (haven't checked the syntax):
public class EmployeeValidator: ValidationDef<Employee>
{
public EmployeeValidator() {
ValidateInstance.By((employee, context) => {
bool isValid = true;
if (string.IsNullOrEmpty(employee.FullName.FirstName)) {
isValid = false;
context.AddInvalid<Employee, string>(
"Please enter a first name.", c => c.FullName.FirstName);
} // Similar for last name
return isValid;
});
}
}
Related
I have the following class
public class School
{
public List<Student> Students { get; set; }
public List<Teacher> Teachers { get; set; }
}
Now i have this method
public bool Evaluate(??)
{
var school = DbContext.Schools.FirstOrDefault();
return school.??.Any(/*some expresions*/)
}
I should be able to pass a value in ?? and use it so that i can use both
return school.Students.Any(/*some expresions*/)
return school.Teachers.Any(/*some expresions*/)
So how can i replace the question marks with Students or Teachers ?
Edit:
public class Student
{
public string FullName { get; set; }
public bool Registered { get; set; }
public bool Passed { get; set; }
}
public class Teacher
{
public string FullName { get; set; }
public bool CanEvaluate { get; set; }
public bool Validator { get; set; }
}
public class DynamicCheckTest
{
public bool MyExpression<T>(List<T> items, string name,
Expression<Func<T, bool>> expression)
{
return items.Any(x => expression.Compile()(x));
}
}
public static bool Check<T>(this List<T> items, Func<T, bool> compiledExp)
{
return items.Any(x => compiledExp(x));
}
Students.Check(x => x.Name == "Mike" && x.Registered); // example
Teachers.Check(x => x.Name == "Jack" && x.CanEvaluate);// example
Now i have to pass the school along which contains both Students and Teachers
But i don't know which one will be called in advance
You could use this method:
public bool Evaluate<T>(Func<School, List<T>> project, Func<T, bool> filter)
{
var school = DbContext.Schools.FirstOrDefault();
return project(school).Any(filter);
}
If we assume that the implementation of Student and Teacher are this:
public class Student
{
public string Name;
}
public class Teacher
{
public string Subject;
}
Then you could do this:
bool hasFred = Evaluate(school => school.Students, student => student.Name == "Fred Nerk");
bool teachArt = Evaluate(school => school.Teachers, teacher => teacher.Subject == "Art");
Addressing the "Pass property name as parameter" request, you could use reflection for that, but I don't think that's a good way to go. Instead, a Func<School, List<TElement>> could be used to select the desired List<> property to evaluate...
public bool Evaluate<TElement>(Func<School, List<TElement>> listSelector)
where TElement : Person
{
School school = DbContext.Schools.FirstOrDefault();
DateTime today = DateTime.Today;
return listSelector(school)
// For example, check if today is the birthday of anyone in the selected list
.Any(person => person.DateOfBirth.Month == today.Month && person.DateOfBirth.Day == today.Day);
}
As #Enigmativity points out, the type constraint is necessary in order to pass much of a meaningful condition to Any(), which also assumes/requires that Student and Teacher have common ancestry, like this...
public abstract class Person
{
public DateTime DateOfBirth
{
get;
}
}
public class Student : Person
{
}
public class Teacher : Person
{
}
You'd then use a lambda expression to specify the desired List<>...
bool isAnyStudentsBirthday = Evaluate(school => school.Students);
bool isAnyTeachersBirthday = Evaluate(school => school.Teachers);
This will work as long as the members you want Any() to consider are available in the constrained type (i.e. Person). If you wanted to filter using members specific to the Student or Teacher class, your best bet would be to use an approach like #Enigmativity's answer, where the filter itself is a parameter and receives the same derived type as the selected List<> stores.
Note that if you ever want to use Evaluate() with some other collection property of School that is not specifically List<>, or just knowing that all Any() needs is an IEnumerable<>, you could change the return type (last type parameter) of the Func<> to something less-restrictive...
Func<School, IList<TElement>>
Func<School, ICollection<TElement>>
Func<School, IEnumerable<TElement>>
I have a BaseScript, in the Script there is a GetAll which not load related entities.
In the DbContext there is LazyLodingEnabled disabled (false).
Following some Code to understand my issue. In this example it doesn't load the user and messageViews when I want to get the messages by language.
BaseScript
public abstract class BaseScript<TEntity> where TEntity : EntityBase
{
public virtual IEnumerable<TEntity> GetAll()
{
using (EntityDbContext ctx = new EntityDbContext())
{
return ctx.Set<TEntity>().ToList();
}
}
}
MessageScript
public class MessageScript : BaseScript<Message>
{
public IEnumerable<Message> GetAllByLanguagePublic(string language)
{
return this.GetAllPublic().Where(x => x.Language == language).ToList();
}
}
Message entity:
public class Message : EntityBase
{
public string Text { get; set; }
public string Language { get; set; }
public virtual User User { get; set; }
public Guid UserId { get; set; }
public virtual ICollection<MessageView> MessageViews { get; set; }
}
I suppose your question is "Why aren't User and MessageViews loaded?" If that is the case, you are answering yourself. If you disable lazy loading, you have to load these properties explicitly, using Include (for example):
Maybe in your BaseScript.GetAll you want to return an IQueryable instead of a IEnumerable so the query will not be exectued yet (do not call ToList()). Then, in your GetAllByLanguagePublic method you could do this:
public class MessageScript : BaseScript<Message>
{
public IEnumerable<Message> GetAllByLanguagePublic(string language)
{
return this.GetAll()
.Include(x => x.User)
.Include(x => x.MessageViews)
.Where(x => x.Language == language).ToList();
}
}
Note that you need to include System.Data.Entity to use Include with lambda expression.
Let's say you have a Domain layer which returns a User object:
public class User
{
public string FirstName{get;set;}
public string LastName{get;set;}
}
Let's say you have an identical class defined in your service layer. What's an elegant method to easily transfer/cast the Domain User object into a service User object?
"Elegant" is subjective. I might just write an extension that converts one to the other.
public static class MappingExtensions
{
public ThisNameSpace.User ToThisUser(this OtherNameSpace.User source)
{
return new ThisNameSpace.User
{
FirstName = source.FirstName,
LastName = source.LastName,
UserId = source.UserId
}
}
}
To me that's the simplest.
You could also use Automapper (add from Nuget.)
Do a one-time configuration:
AutoMapper.Mapper.Initialize(c=>c.CreateMap<User,Other.User>());
and then you can call it to map an instance of User to a new instance of Other.User.
var other = AutoMapper.Mapper.Map<Other.User>(user);
It works without specifying the mapping for individual properties if the property names and types are identical.
You can use reflection:
using System.Windows.Forms;
using System.Reflection;
namespace First
{
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserID { get; set; }
public User()
{
}
}
}
namespace Second
{
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserID { get; set; }
public User()
{
}
}
}
namespace YetAnotherNamespace
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
First.User user = new First.User()
{
FirstName = "John",
LastName = "Public",
UserID = "jpublic#mydomain.com"
};
Second.User newUser = ConvertUser(user);
}
public Second.User ConvertUser(First.User oldUser)
{
Second.User newUser = new Second.User();
foreach (PropertyInfo prop in oldUser.GetType().GetProperties())
{
string propertyName = prop.Name;
object propertyValue = prop.GetValue(oldUser);
newUser.GetType().GetProperty(propertyName).SetValue(newUser, propertyValue);
}
return newUser;
}
}
}
In this code sample, when I instantiate the form, I create a First.User and convert it to a Second.User.
This is tested and working (as a strongly-typed method). I think you can make it more generic and accept and return an "object", and it will just throw an exception if the properties don't match up. Also keep in mind that reflection tends to be slow - this may not be the most scalable solution.
Another approach would be serializing to json and then deserializing to the other type.
I'm trying to add a new sub-entity, product component ProductRevComp to an existing entity ProductRev. However when I retrieve an instance of the ProductRev class, the Comps collection is never populated (even when explicitly Including() it). I BELIEVE I have mapped everything correctly, but it has taken more fiddling than I want and this is the most likely place for a mistake to be hiding. However profiling the SQL statements show the relevent columns are being populated with the correct data.
Checking db.ProductRevComps (i.e. the DbSet of all my comps) shows the records can be loaded, and that mapping is working as expected.
Mappings:
public class ProductRevConfiguration : EntityTypeConfiguration<ProductRev>
{
public ProductRevConfiguration()
{
HasKey(p => p.ProductRevId);
HasMany(p => p.Comps).WithRequired().HasForeignKey(p => p.ParentProductRevId);
Ignore(p => p.ProgrammedParts);
}
}
public class ProductRevCompConfiguration : EntityTypeConfiguration<ProductRevComp>
{
public ProductRevCompConfiguration()
{
HasKey(p => new { p.ParentProductRevId, p.CompProductRevId });
HasRequired(p => p.ParentProductRev).WithMany().HasForeignKey(p => p.ParentProductRevId);
HasRequired(p => p.CompProductRev).WithMany().HasForeignKey(p => p.CompProductRevId);
}
}
Product entity (amazingly simplified):
public class ProductRev
{
public string ProductRevId { get; set; }
public virtual List<ProductRevComp> Comps { get; set; }
public virtual List<ProductRevComp> ProgrammedParts { get { return Comps; } }//Will be filtered once I get this working
public ProductRev() { }
}
Comp entity:
public class ProductRevComp
{
public string ParentProductRevId { get; set; }
public virtual ProductRev ParentProductRev { get; set; }
public string CompProductRevId { get; set; }
public virtual ProductRev CompProductRev { get; set; }
public int CompTypeValue { get; set; }
public ProductRevCompType CompType
{
get { return (ProductRevCompType)CompTypeValue; }
set { CompTypeValue = (int)value; }
}
public enum ProductRevCompType { ProgrammedPart = 1 };
public ProductRevComp() { }
public override string ToString()
{
return base.ToString();
}
}
Removing the extra prog parts collection doesn't change anything.
How can I get the ProductRev entity to populate the Comps property without resorting to a manual DB hit?
(Must run as the office is closing and I don't have a key - I hope I have included all details, please comment if anything is missing.)
I'm trying to get Fluent NHibernate to map a collection for me. My class definitions are as follows:
public abstract class Team
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class ClientTeam : Team
{
public virtual IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Identifiers { get; set; }
}
My mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Table("Team");
Id(x => x.Id).GeneratedBy.Assigned();
Map(t => t.TeamName);
}
}
public class ClientTeamMap : SubclassMap<ClientTeam>
{
public ClientTeamMap()
{
HasMany(t => t.Clients);
}
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Table("Client");
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Identifiers);
}
}
I've built a unit test that instantiates a team and then attempts to persist it (the test base has dependency configuration, etc. in it):
public class TeamMapTester : DataTestBase
{
[Test]
public void Should_persist_and_reload_team()
{
var team = new ClientTeamDetail
{
Id = Guid.NewGuid(),
TeamName = "Team Rocket",
Clients = new[]
{
new ClientDetail {ClientName = "Client1", ClientIdentifiers = "1,2,3"}
}
};
using (ISession session = GetSession())
{
session.SaveOrUpdate(team);
session.Flush();
}
AssertObjectWasPersisted(team);
}
}
When I run the test, I get this error:
SetUp : FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> NHibernate.MappingException: Could not compile the mapping document: (XmlDocument)
----> NHibernate.PropertyNotFoundException : Could not find field '_clients' in class 'ClientTeam'`
I've looked through the NHibernate documentation and done some google searching, but I can't find anything that appears to address this issue. The documentation for Fluent NHibernate's Referencing methods explicitly uses auto properties, so I'm sure that's not the issue.
Why might NHibernate think that _clients is the field it should map in this case?
And the reason turns out to be: Conventions.
The Fluent mappings were set up to try to enforce read-only collection properties, by requiring a backing field. The ICollectionConvention in question:
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
instance.Fetch.Join();
instance.Not.LazyLoad();
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
which requires that collection backing fields be camelCased and start with an underscore.