Fluent NHibernate automap list of strings with nvarchar(max) - c#

I am using Fluent NHibernate 1.2 for NHibernate 3.1. I have a class:
public class Marks
{
public virtual int Id { get; set; }
public virtual IList<string> Answers { get; set; }
}
In the mapping for the Marks class, I have:
HasMany(m => m.Answers).Element("Value");
When the tables are created, an "Answers" table get created with the following columns:
Marks_id (FK, int, not null)
Value (nvarchar(255), null)
What I would like to do is have the Value be nvarchar(max). I'd prefer not doing this for every string in every class, just for this one class.
I have looked at these posts: first, second, third, but haven't found anything yet that helps.
Thanks in advance for any help you can offer. If you need additional information, please let me know.
Edit:
This is the code that resolves the issue:
HasMany(x => x.Answers).Element("Value", x => x.Columns.Single().Length = 4001);

You can force mapping string to longer column at each column level in mapping either by using CustomSqlType("nvarchar(max)") or, bit more universally, by setting Length(4001) (SQL Server magic number, above which it creates nvarchar(max) automatically).
To apply it automatically for all string columns in your entities, you can write your own FluentNHibernate convention:
public class LongStringConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(string));
}
public void Apply(IPropertyInstance instance)
{
instance.Length(4001);
}
}
And register it in mapping container i.e. like that:
Fluently.Configure()
.Mappings(...)
.Conventions.Add<LongStringConvention>()
To apply it for collection of strings, you can use custom element mappings:
HasMany(x => x.Answers).Element("Value", x => x.Columns.Single().Length = 4001);

Came across this issue myself and the above answers were most helpful in pointing me in the right direction...but I'm using a slightly newer version of FluentNH.
For Fluent NHibernate 1.3 and NH 3.3.1, the correct code is:
HasMany(x => x.Answers).Element("Value", x => x.Length(4001));

The answers above only work for older version of nhibernate.
If you try HasMany(x => x.Answers).Element("Value", x => x.Length(4001)); you will get the following:
Error Property or indexer
'FluentNHibernate.MappingModel.ColumnMapping.Length' cannot be
assigned to -- it is read only
The correct way to do this now is (NHibernate 4.0.2.4, FluentNHibernate 2.0.1.0):
HasMany(m => m.Answers).Element("Value", e => e.Length(4001))

Related

EntityFramework code-first inheritance with eager include relationship on derived class

I don't think using TPH or TPT inheritance method influence on this.
My goal is to have a single method that load everything from the database with mixed types of entities that may have separate relationship depending on the type.
Let's take this code-first model (simplistic mock-up to represent my problem):
public abstract class Entity
{
public int ID { get; set; }
public string Name { get; set; }
}
public abstract class EntityWithInfo : Entity
{
public AdditionalInformation Info { get; set; }
}
public class DerivedEntityWithInfo : EntityWithInfo
{
}
public class DerivedEntityWithInfo2 : EntityWithInfo
{
}
public class DerivedEntityWithoutInfo : Entity
{
}
public class AdditionalInformation
{
public int ID { get; set; }
public int SomeProperty { get; set; }
}
And the Fluent API Configuration:
modelBuilder.Entity<Entity>()
.HasKey(e => e.ID)
.Map<DerivedEntityWithInfo>(m => m.Requires("Type").HasValue(1)
.Map<DerivedEntityWithInfo2>(m => m.Requires("Type").HasValue(2)
.Map<DerivedEntityWithoutInfo>(m => m.Requires("Type").HasValue(3);
modelBuilder.Entity<EntityWithInfo>()
.HasRequired(e => e.Info)
.WithRequiredPrincipal()
.Map(e => e.MapKey("Entity_FK"));
modelBuilder.Entity<AdditionalInformation>()
.HasKey(e => e.ID);
Or visually:
With the SQL schema being simple:
Table Entity with: Id, Type, Name
Table AdditionalInformation with: Id, SomeProperty, Entity_FK
Now, I want to be able to do something like:
context.Entity.Where(t => t.ID = 304 || t.ID = 512).ToList();
This gives me correctly the list of all entities, and properly typed. But, of course the Info property is always null. Disabling LazyLoading and removing virtual don't force to load it either, as I understood I absolutely need to have a .Include(t => t.Info) line in there.
I know I can call
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Where(t => t.ID = 304 || t.ID = 512).ToList();
But then I will only get entities derived of EntityWithInfo, and not the DerivedEntityWithoutInfo ones.
So let's try with an union:
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Cast<Entity>()
.Union(context.Entity.OfType<DerivedEntityWithoutInfo>().Cast<Entity>())
.Where(t => t.ID == 719 || t.ID == 402);
This does not work, it tells me "Entity does not declare navigation property Info".
And also, I guess this would create one hell of a SQL query.
In fact, the very reason why I'm on this is because of a very old project that was using LINQ2SQL doing the equivalent with "LoadWith" options generating an abusive SQL query (duplicate the relationship table in a join for every inherited type). Loading a single entity take more times than loading the entire table of thousands of elements raw without hierarchy. So I was trying to see if porting to EntityFramework would generate a more optimal SQL query.
So, is there a way to do this or we are simply trying to do something the wrong way? This doesn't seems to be a very popular method to have both inheritance, relationship on derived classes, and eager loading because I find pretty much no resource on that online.
At this point, any suggestion on how to create this object model from this database model would be appreciated. Thanks
This is now supported in EF Core 2.1. Now to see if the resulting query is not too much performance-hungry.

Automapper-Map to a string

I tried creating a mapping to a string using the following CreateMap():
Mapper.CreateMap<MyComplexType, string>()
.ConvertUsing(c => c.Name);
But when I try to use this mapping, I get the following error:
Type 'System.String' does not have a default constructor
That makes sense, but I've been reading around and supposedly this should work. Is there something else I have to do?
In my case I was using
.ProjectTo<>()
To project directly from a DBContext collection (EF 6) to my DTO e.g.
db.Configuration.LazyLoadingEnabled = false;
prospects = db.Prospects.Where([my where lambda]).ProjectTo<ProspectDTO>().ToList();
With a destination with an IEnumerable<string> property coming from a M-M related table i.e.
public class ProspectDTO
{
public IEnumerable<string> Brands { get; set; }
}
and my solution was mapping as follows
AutoMapper.Mapper.CreateMap<Prospect, ProspectDTO>().ForMember(dest => dest.Brands, opts => opts.MapFrom(src => src.Brands.Select(b => b.Name)));
N.B. I am using the ProjectTo<> like this to avoid the common lazy loading select n+1 problem and ensure decent (quick) sql runs against the DB, and I have all the related table data I need. Excellent.
Thanks Jimmy Bogard you rockstar !!!

LinQ to SQL ignoring != null on virtual properties

I've been using LinQ to SQL with EF for a while now and have just stumbled on some strange behaviour that hopefully someone may be able to explain for me.
I'm performing a LinQ query on a database context that has a POCO entity with virtual properties for related entities. I'm using a where clause to eliminate instances where that entity is null. I'm using Lazy Loading.
return this.runtimeContext.FatalExceptionLogs.Where(l => l.RuntimeToDesignJuicerRelationship != null);
What I'm finding is that when my query is evaluated LinQ to SQL seems to entirely ignore my condition that the virtual property is null as if I'd never included this check at all. Instead it returns all records in my dbset named FatalExceptionLogs.
Now I have a simple workaround for this which is to first load the data into memory using .ToList() then
This looks like so:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
Now the check is performed in memory and all instances where the virtual property is null are returned (because there is no corresponding record as the id which is used for the join is nullable) and we're all good.
I have also considered:
Checking if the id that is joined on is null but unfortunately I can't garauntee that the referential integrity of the table has been maintained as there is no foreign key applied urgh!.
Checking if there are any records in the other table with the matching id, but that could be rather inefficient.
So I have a way of working around this but I'd really like to understand why LinQ to Sql is doing this and what other options there are, can anyone help?
The full code if it helps is below though I've cut it down for this example:
The query:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
The entity:
public class FatalExceptionLog
{
public int Id { get; set; }
public int? RuntimeJuicerId { get; set; }
public virtual RuntimeToDesignJuicerRelationship RuntimeToDesignJuicerRelationship { get; set; }
}
The mapping:
public class FatalExceptionLogMap : EntityTypeConfiguration<FatalExceptionLog>
{
public FatalExceptionLogMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("FatalExceptionLogging");
this.Property(t => t.RuntimeJuicerId).HasColumnName("JuicerLiveID");
this.HasRequired(t => t.RuntimeToDesignJuicerRelationship)
.WithMany(t => t.FatalExceptionLogs)
.HasForeignKey(t => t.RuntimeJuicerId);
}
}
Why NOT just do the normal joining?
return this.runtimeContext.FatalExceptionLogs.Where(
l => runtimeContext.RuntimeJuicers.Any(
y => y.RuntimeJuicerId == l.RuntimeJuicerId
)
);

Self referencing many to many relationships in Fluent NHibernate automapping automapping to 1:n and not n:n

The title pretty much explains it all, I have a Member object that references 'Friends' who are also type Member.
public class Member : Entity
{
public Member()
{
Friends = new List<Member>();
}
public virtual IList<Member> Friends
{
get; set;
}
}
The schema generation tool makes it a 1:n relationship while it should be a n:n relationship i.e. a column is added to the member table called member_id and no connecting table is created.
Is there any way to make a Self referencing many to many relationships in Fluent NHibernate?
I tried using an override that I got as an answer before:
public class MemberOverride : IAutoMappingOverride<Member>
{
public void Override(AutoMapping<Member> mapping)
{
mapping.HasManyToMany(m => m.Friends)
.Table("MemberFriendsLinkTable");
}
}
but I get the error message:
"NHibernate.MappingException: Repeated column in mapping for collection: Proj.BO.Member.Friends column: Member_id"
Thanks
EDIT: I found the answer, it's to put:
mapping.HasManyToMany(m => m.Friends).ParentKeyColumn("Member_Id").ChildKeyColumn("Friend_Id")
.Table("MemberFriendsLinkTable").Inverse().Cascade.SaveUpdate();
So that I don't have to see this question at the top of the "Unanswered NHibernate Questions" list anymore...
Eitan, the asker, discovered the solution to his own problem. He needed to specify the ParentKeyColumn and ChildKeyColumn like so:
EDIT: I found the answer, it's to put:
mapping.HasManyToMany(m => m.Friends)
.ParentKeyColumn("Member_Id")
.ChildKeyColumn("Friend_Id")
.Table("MemberFriendsLinkTable")
.Inverse()
.Cascade.SaveUpdate();
FluentNHibernate by default names foreign key columns like so: {className}_Id. Since both ends of the many-to-many were of the same type, it wanted to use the same column name, Member_Id for both columns. Explicitly naming the columns circumvents this problem.
References(x => x.Parent)
.Class<Parent>()
.Access.Property()
.Cascade.None()
.LazyLoad()
.Not.Insert()
.Not.Update()
.Columns("PARENT_ID");
HasMany(x => x.Children)
.Access.Property()
.AsBag()
.Cascade.SaveUpdate()
.LazyLoad()
.Inverse()
.Generic()
.KeyColumns.Add("PARENT_ID", mapping => mapping.Name("PARENT_ID")
.SqlType("NUMBER")
.Not.Nullable());
Well I understand that, I have similar kind of issue with little bit of change. Can you please try to answer the question to the link
Fluent nhibernate m-to-m mapping with external table

How to set discriminate column type for subclass with FNH?

What is the new SetAttribute() in FNH mapping? I need to set my discriminator value on subclass because String is not preferred - old post
with NH 2.1.2.4000, FNH 1.1.0.689
public class BaseBuildingMap : ClassMap<BaseBuilding>
{
public BaseBuildingMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn<int>("BuildingType", -1);
}
}
public class PowerStationMap : SubclassMap<PowerStation>
{
public PowerStationMap()
{
Map(x => x.ElectricityProduction);
}
}
NHibernate.MappingException: Could not format discriminator value to SQL string of entity Model.Test.PowerStation ---> System.FormatException: Input string was not in a correct format.
I need to set SetAttribute("discriminator-value", "-1"); but there is no such method.
EDIT 1
Question: How to set discriminate column type for subclass with FNH?
public class PowerStationMap : SubclassMap<PowerStation>
{
public PowerStationMap()
{
DiscriminatorValue((int)1);
Map(x => x.ElectricityProduction);
}
}
I've finally found my answer, it's
SubclassMap<T>::DiscriminatorValue(object discriminatorValue);
From Fluent NHibernate 1.0 Release Notes
Removed SetAttribute - SetAttribute was a stop-gap measure to allow people to use Fluent NHibernate when we didn't support the attributes they needed. We've now gone to a great length to support all of the main attributes in the fluent interface, so you shouldn't need this anymore. If there are any attributes you need that we've missed, let us know (or even better, send us a pull request/patch)

Categories