Just getting my feet wet with some Fluent NHibernate AutoMap conventions, and ran into something I couldn't figure out. I assume I'm just not looking in the right place...
Basically trying to enforce NOT-NULL on the "many" side of the one to many relationship.
It seems, using the automapping, it always makes the parent property Id nullable in the database.
I did some searching on StackOverFlow and found similar questions, but nothing relating to AutoMapping and Conventions though (unless I missed it).
Quick example...
public class Group // One Group
{
public Group() { this.Jobs = new List<Job>(); }
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Job> Jobs { get; protected set; }
}
public class Job // Has many Jobs
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
// Trying to make this field not-nullable in the database.
public virtual Group Group { get; set; }
}
I thought I'd be able to just create a convention like...
public class OneToManyConvention : IHasOneConvention
{
public void Apply(IOneToOneInstance instance)
{
// Nullable() isn't a valid method...
instance.Not.Nullable();
}
}
But it seems IOneToOnInstance doesn't have a Nullable() method. I can do this if I create a Map file for Job, but trying to avoid any Map files and stick with auto-mapping.
I came across this link on the Fluent group list describing something similar.
Which describes something like this...
public class NotNullPropertyConvention : IPropertyConvention
{
public bool Accept(IProperty target)
{
return true;
}
public void Apply(IProperty target)
{
target.Not.Nullable();
}
}
But that raises the questions of...
1) How would I determine IProperty to be a Job (or any child property that is a link back to the parent)
2) It made a mention on that page that using this would override my manual overrides, eg. if a very specific property link needed to be NULL. Which would be an issue (if it's still an issue, but can't test without figuring out #1 first)
Any ideas on this? Am I just missing something?
Update 1
Still no go. Even the following still doesn't enforce Not-Nullable in the database schema...
public class FluentConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
}
It does for all of the other fields though...
/shrug
Any ideas?
Update 2
While this isn't the answer I was looking for, I did find a work around...
I was using NHibernate Validator assembly, and within that assembly there is a [NotNull] attribute. If I decorated my class with the Validator attribute, and associated the ValidationEngine to NHibernate before the schema creation, it would tag the FK database column as Not-Nullable.
public class Job // Has many Jobs
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
[NHibernate.Validator.Constraints.NotNull]
public virtual Group Group { get; set; }
}
If anyone needs the full code for the NHibernate + ValidationEngine initialization, just let me know.
Still looking for a way to do it using the pure mapping convention route though if anyone has any info...
Thanks!
You can override the auto-mapped properties as part of your AutoMap in Fluenttly.Configure().
So you can do this:
.Override<Job>(map => map.References(x => x.Group).Not.Nullable())
It's not exactly convenient if you have a lot of classes that need this though.
Edit:
You can also specify the override in a class that implements IAutoMappingOverride like so:
public class JobMappingOverride : IAutoMappingOverride<Job>
{
public void Override(AutoMapping<Job> mapping)
{
mapping.References(x => x.Group).Not.Nullable();
}
}
and include it like so:
.UseOverridesFromAssemblyOf<JobMappingOverride>()
This would keep your fluent configuration a little cleaner.
It seems that IPropertyConvention is only called on simple properties of your classes. If your property references another class, you need to use IReferenceConvention too.
Try this:
public class FluentConvention : IPropertyConvention, IReferenceConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
public void Apply(IManyToOneInstance instance)
{
instance.Not.Nullable();
}
}
Related
I have an entity that I want managed only through it's parent entity. Consider purchased Items, a list of Vendors, and Approved Vendors for an Item:
// DOMAIN
public class Item {
public string Name { get; set; }
public virtual ICollection<ApprovedVendor> ApprovedVendors { get; set; }
}
public class Vendor {
public string Name {get; set; }
}
public class ApprovedVendor {
public int ItemID {get;set;}
public int VendorID {get;set;}
public decimal? Cost {get;set;}
public virtual Item Item {get;set;}
public virtual Vendor Vendor {get;set;}
}
// DATA (DbContext mappings)
public DbSet<ApprovedVendor> ApprovedVendors {get;set;}
public DbSet<Item> Items {get;set;}
public DbSet<Vendor> Vendors {get;set;}
// fluent entity mappings as per usual
What I'm trying to do is remove access to the context.ApprovedVendors from external assemblies, thus allowing approved vendors to only be managed through an Item. However, I still need the EF6 mappings as appropriate. Additionally, for integration tests to ensure the model builds from the connected database, I must access the DbSet<ApprovedVendor> from a test project. Thus, I made the following changes:
// in PDB.Data AssemblyInfo.cs
[assembly: InternalsVisibleTo("PDB.Data.Tests.Integration")]
// in dbcontext
internal DbSet<ApprovedVendor> ApprovedVendors {get;set;}
// in PDB.Data.Tests.Integration
[TestMethod]
public void BuildsApprovedVendor() {
var sut = _context.ApprovedVendors.FirstOrDefault();
if (sut == null) {
Assert.Inconclusive();
}
Assert.IsInstanceOfType(sut, typeof(Domain.Items.ApprovedVendor));
}
I thought this would do, but it appears that the DbSet<ApprovedVendor> must be public, as I get the following error running the test:
PDB.Data.Tests.Integration.ModelBuilding.BuildsApprovedVendor threw exception:
System.ArgumentNullException: Value cannot be null.
Parameter name: source
If I change internal back to public, everything works fine (except now my DbSet is public again...)
Can I do this (am I missing something), or am I stuck with throwing an Obsolete attribute on a public DbSet and hoping future devs pay attention?
You can define dbset like this:
public DbSet<ApprovedVendor> ApprovedVendors {internal get;set;}
This will prevent doing anything with it from another assemblies (because getter is internal), except setting it, which usually doesn't make sense anyway. At the same time, because setter is still public - EF will be able to map that set correctly.
I'm implementing a POCO in my project that represents a row in my database table. I'd like to modify one of the values in the constructor.
Unfortunately, it seems that the values are populated only after the constructor is run, so there's no way for me to perform my required logic. Is this a bug or by design?
I should probably mention that I'm using Code First.
public partial class CheckpointValue
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("saljare")]
public int SalesAgentId { get; set; }
[Column("volym")]
public int Value { get; set; }
[Column("datum")]
public DateTime Date { get; set; }
[Column("typ")]
public string Type { get; set; }
public CheckpointValue()
{
// Values empty... Why haven't they been populated when the constructor is run?
}
}
Unfortunately, it seems that the values are populated only after the
constructor is run, so there's no way for me to perform my required
logic. Is this a bug or by design?
This is by design. BTW, how you would be able to get these properties already populated during construction-time without providing constructor arguments?.
Maybe you're trying to implement your logic in the wrong place. If you're looking for implementing business rules, domain validation or who knows what, it should be done in another class. For example, your repository would be a good place to do things before returning the requested object.
public CheckpointValue GetById(Guid id)
{
CheckpointValue checkpointValue = YourDbContext.CheckpointValues.Find(id);
// Implement here what you wanted in your class constructor...
return checkpointValue;
}
I have the following two entities (using Code First) in my application:
public class Note
{
public int NoteId { get; set; }
public string Text { get; set; }
}
public class Decision
{
// PK/FK
public int NoteId { get; set; }
// other fields ...
public virtual Note Note { get; set; }
}
I configured my relationship like this:
modelBuilder.Entity<Decision>().HasRequired(d => d.Note).WithOptional();
A Decision must have a note but a Note does not always have a decision. A 1:1 mapping with one side being optional.
I would like a property on my note that lets me know if there is a decision for it. Something like:
public bool HasDecision
{
get
{
// not sure what to do here
}
}
Is there a way to do this without having Decision be a lazy loaded property on Note?
You would need to do an explicite query. There is no such thing like "lazy loading proxies for scalar properties". Lazy loading is only supported for navigation properties. Your entity must have a reference to a context if you want to have HasDecision as a property on the entity. I would prefer to create a repository or service method like so:
public bool HasDecision(Note note)
{
return _context.Decisions.Any(d => d.NoteId == note.NoteId);
}
I've read the nested mapping wiki page but it appears to not like multiple levels of nesting. I've got the following maps created and classes defined.
AutoMapper.Mapper.CreateMap<Address, AddressDTO>();
AutoMapper.Mapper.CreateMap<MatchCompanyRequest, MatchCompanyRequestDTO>();
public class MatchCompanyRequest
{
Address Address {get;set;}
}
public class MatchCompanyRequestDTO
{
public CompanyInformationDTO {get;set;}
}
public class CompanyInformationDTO {get;set;}
{
public string CompanyName {get;set;}
public AddressDTO Address {get;set;}
}
But the following code...
// works
matchCompanyRequestDTO.companyInformationDTO.Address =
AutoMapper.Mapper.Map<Address, AddressDTO>(matchCompanyRequest.Address);
// fails
matchCompanyRequestDTO =
AutoMapper.Mapper
.Map<MatchCompanyRequest, MatchCompanyRequestDTO>(matchCompanyRequest);
Does this deep nesting work and I have it configured improperly? Or is this kind of nesting not yet supported?
-- Edit
For anyone interested, I am not in control of the DTOs.
It lacks the mapping from Address to CompanyInformationDTO, as those objects are on the same nest-level.
The map is created for MatchCompanyRequest -> MatchCompanyRequestDTO, but it is unable to figure out whether it can map Address to CompanyInformationDTO.
So your MatchCompanyRequestDTO could in fact have same declaration as your CompanyInformationDTO:
public class MatchCompanyRequestDTO
{
public string CompanyName {get;set;}
public AddressDTO Address {get;set;}
}
This of course only affects you if you want to use automatic mapping. You still can configure your maps manually, but it seems like the DTOs should be fixed instead, let's try anyway:
public class CustomResolver : ValueResolver<Address, CompanyInformationDTO>
{
protected override CompanyInformationDTO ResolveCore(Address source)
{
return new CompanyInformationDTO() { Address = Mapper.Map<Address, AddressDTO>(source) };
}
}
// ...
AutoMapper.Mapper.CreateMap<MatchCompanyRequest, MatchCompanyRequestDTO>()
.ForMember(dest => dest.companyInformationDTO, opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.Address)); // here we are telling to use our custom resolver that converts Address into CompanyInformationDTO
The important thing is you define how deeper is your navigation, to previne the stackoverflow problems. Imagine this possibility:
You have 2 entities Users and Notifications in NxN model (And
you have DTOs object to represent that), when you user auto mapper
without set MaxDepth in you mapper expression, "Houston we have a
problem" :).
The code below show a workaround to resolve this for all Mappers. If you want can be defined to each mapper. Like this Question
Solution 1 (Global Definition)
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(mapperConfiguration =>
{
mapperConfiguration.AddProfile<DomainModelToYourDTOsMappingProfile>();
mapperConfiguration.AddProfile<YourDTOsToDomainModelMappingProfile>();
mapperConfiguration.AllowNullCollections = true;
mapperConfiguration.ForAllMaps(
(mapType, mapperExpression) => {
mapperExpression.MaxDepth(1);
});
}
}
Solution 2 (For each Mapper)
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.CreateMap<User, DTOsModel>()
.MaxDepth(1);
}
}
Consider the following instead:
public class MatchCompanyRequest
{
Address Address {get;set;}
}
public class MatchCompanyRequestDTO
{
public string Name {get;set;}
public AddressDTO Address {get;set;}
}
public class AddressDTO
{
....
}
Your DTO objects need to have the same structure as your domain objects for the default mapping conventions to work in AutoMapper.
Look at this: https://github.com/AutoMapper/AutoMapper/wiki/Projection It will explain the Projection for you, you could customize it to work the way you have it.
I'm trying to figure out what the convention would be for a value object list, in this case an IList. Here a code fragment for my domain model:
public class RegionSetting : Entity {
public virtual bool Required { get; set; }
public virtual string Name { get; set; }
public virtual IList<string> Options { get; set; }
}
My automapping is set to:
public class RegionSettingMap : IAutoMappingOverride<RegionSetting> {
public void Override(AutoMapping<RegionSetting> mapping) {
mapping
.HasMany(x => x.Options).Element("Options")
.Table("RegionSettingOptions")
.KeyColumn("RegionSettingId");
}
}
I'd like to make the .Table() and .KeyColumn() overrides into a convention so that I don't have to do that everywhere I'm using IList<string>. I thought that I could create an IHasManyConvention, but it doesn't seem to affect this mapping. I set a breakpoint in my custom HasManyConvention class, but it doesn't break for the Options property. Could anyone tell me what convention I should be using to automate this override?
Using an IHasManyConvention should've worked. Try an IBagConvention, see if that works. If not, there's a bug in there.