I have a situation where my primary key is a char(2) in SqlServer 2008, and I want to reference it in a one-to-many relationship, but the ManyToOneBuilder (which is returned by ClassMap<>.References()) doesn't have a CustomSqlType() method. Specifically:
public class State
{
// state FIPS code is 2 characters
public virtual string StateCode { get; set; }
public virtual ICollection<County> { get; set; }
}
public class County
{
// state-county FIPS code is 5 characters
public virtual string StateCountyCode { get; set; }
public virtual State State { get; set; }
}
public class StateMap : ClassMap<State>
{
public StateMap()
{
Id(e => e.StateCode).CustomSqlType("char(2)").GeneratedBy.Assigned();
}
}
public class CountyMap : ClassMap<County>
{
public CountyMap()
{
Id(e => e.StateCountyCode).CustomSqlType("char(5)").GeneratedBy.Assigned();
References(e => e.State, "StateCode")
// Here's what I want to do, but can't because the method is not
// implemented on the class ManyToOneBuilder:
.CustomSqlType("char(2)");
}
}
Is there any way to accomplish this without modifying the ManyToOneBuilder? Is there a way to automatically map the FK (i.e. County.StateCode) to the correct type? It's trivial to add CustomSqlType to ManyToOneBuilder, but is that the right thing to do?
Keep your mapping definition as is, add your "State" column definition with
.CustomSqlType("char(2)")
and set for this column Insert=false and update=false.
I've the same problem and in AutoMapping I use this code:
mapping.Map(x => x.IdUniArticolo)
.CustomSqlType("varchar(50)")
.Not.Insert().Not.Update();
mapping.References(x => x.Articolo)
.Column("IdUniArticolo").PropertyRef("IdUniArticolo");
Keep in mind that if NHibernate itself doesn't support it, then Fluent NHibernate can't, and I don't NHibernate supports the scenario you have. I had a similar problem in that I had a 2 column composite key on a table and on one of the fields, I wanted to use an enumerated type which had a custom IUserType to translate it to its appropriate code value in the DB. Couldn't do it, so I was stuck keeping the property of the string type rather than the enumerated type.
Related
I have following object model:
public class SharingRelation:BaseEntity
{
public Guid? Code { get; set; }
public string Value { get; set; }
}
public class SecondLevelShareEntity : BaseEntity
{
public string Name { get; set; }
public Guid? SharingCode { get; set; }
public List<SharingRelation> SharingRelations { get; set; }
}
In my database (it may be poor db design but I need to answer this question for research), SharingRelation is some sort of dependent entity of SecondLevelShareEntity on Code == SharingCode values. I can have two entities of type SecondLevelShareEntity with same SharingCode value. So, for each of them I need to get all related SharingRelation objects depending on Code and SharingCode values. I can do it using SQL and join on this columns. But how can I do it using EF Core and navigation properties (I want to get all dependent entities using Include() for example)? When I configure my entities like this
public class SharingRelationEntityTypeConfiguration : BaseEntityTypeConfiguration<SharingRelation>
{
public override void Configure(EntityTypeBuilder<SharingRelation> builder)
{
base.Configure(builder);
builder.HasOne<SecondLevelShareEntity>().WithMany(x => x.SharingRelations).HasForeignKey(x => x.Code)
.HasPrincipalKey(x => x.SharingCode);
}
}
EF Core creates foreign key and marks it unique. I am obviously getting an error that that is impossible to have several SecondLevelShareEntity with the same SharingCode
System.InvalidOperationException : The instance of entity type 'SecondLevelShareEntity' cannot be tracked because another instance with the key value '{SharingCode: 8a4da9b3-4b8e-4c91-b0e3-e9135adb9c66}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
How can I avoid creation of foreign key, but keep using navigation properties (as far, as I see normal queries with navigations generate simple JOIN statements)
UPDATED I can provide real data in database. SecondLevelShareEntity table looks like this:
_id Name SharingCode
----------------------------------------------------------------------
1 "firstSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
And SharingRelation table looks like this:
_id Value Code
----------------------------------------------------------------------
1 "firstSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
I'm discovering nhibernate right now. Thus my question is maybe very stupid :)
What I'm trying to do (I'm working with a legacy database) is to get an entity which some of its data are coming from a table value function.
My entity is the following
public class Entity
{
public virtual int Id { get; protected set; }
....
public virtual int AccessRightId { get; set; }
}
where AccessRightId comes from the table value function (fp_AccessRight('userId'))
I have the following mapping
public class EntityMap : ClassMap<Entity>
{
public EntityMap ()
{
this.Id(entity => entity .Id);
this.Join(
"fp_AccessRight('userId')",
join =>
{
join.Fetch.Join();
join.KeyColumn("EntityId");
join.Map(t => t.AccessRightId, "AccessRightType");
join.Table();
});
}
}
Unfortunately, I'm not able to substitute 'userId' by any value.
Thanks.
Is there a way to do it?
I finally solved it.
The trick was to make the join like this :
this.Join("fp_ACCOUNT_ACL(:AclFilter.userId)"
and then simpley to enable the filter
this.session.EnableFilter("AclFilter").SetParameter("userId", "bdd#5");
I found an example here.
We are using Fluent NH with convention based mapping. I have the following:
public class Foo() : Entity
{
public BarComponent PrimaryBar { get; set; }
public BarComponent SecondaryBar { get; set; }
}
public class BarComponent
{
public string Name { get; set; }
}
I have it to the point where it will create the foo table with a single name field. I've tried the following Override and it doesn't work.
public class FooOverride : IAutoMappingOverride<Foo>
{
public void Override(AutoMapping<Foo> mapping)
{
mapping.Component(x => x.PrimaryBar).ColumnPrefix("primary");
mapping.Component(x => x.SecondaryBar).ColumnPrefix("secondary");
}
}
Do I really need to do a full override mapping or can what I have here be made to work somehow?
I ran into this a couple of years ago when I started with FNH. It's one of the few scenarios I've seen where FNH Automapping does not "just work".
The approach that was suggested to me at the time, which I've used successfully (with entities however, not components) is to create empty, intermediate entities, and reference them in the descendant class.
In your case, you could create two new, empty classes that inherit from BarComponent (say, PrimaryBarComponent and SecondaryBarComponent).
Then, in your Foo class, declare them as:
public PrimaryBarComponent PrimaryBar { get; set; }
public SecondaryBarComponent SecondaryBar { get; set; }
This is a kluge, in my opinion, but it works fine with entities and lists of entities, and does not require any overrides or conventions.
I've never used components with FNH, so I don't know if a similar approach will work, but it might be worth investigating.
I ended up getting the way I have described in the question working. It turned out to be a problem with our AutoMappingConfiguration which inherits from DefaultAutomappingConfiguration. We weren't identifying Components properly.
I have a class that has an enum type indicating whether the message type is Email or Sms. The enum type is defined:
public enum ReminderType
{
Email = 1,
Sms = 2
}
The class that utilizes this type looks like:
public class Reminder : EntityBase
{
public virtual string Origin { get; set; }
public virtual string Recipient { get; set; }
public virtual ReminderType Type { get; set; }
public virtual Business Business { get; set; }
public virtual DateTime Created { get; set; }
public Reminder()
{
Created = DateTime.UtcNow;
}
}
When I try to persist an entity of type Reminder to the database however, I get the following error:
System.Data.SqlClient.SqlException (0x80131904): Conversion failed when converting the nvarchar value 'Email' to data type int.
The backing field is of type int, so I'm not sure why NHibernate is trying to map the string representation by default. I'm using Fluent NHibernate, and the relevant mapping code is:
mappings.Override<Reminder>(map =>
{
map.Map(x => x.Type).Column("Type")
});
I'm pretty sure the default behavior of NHibernate is to map enums as ints, so why is it not doing so in this case? I'm using SQL Server 2005, if that matters.
I am doing the same thing and got it working like so...
In my case EmployeeType is the enum class
Map(x => x.EmployeeType, "EmployeeType_Id").CustomType(typeof (EmployeeType));
I don't know why this person keeps posting and then deleting their comment or answer, but the link they provided () does answer my question. I opted not to go with a full blow class definition for the convention, but rather, an inline convention in the mappings code, like so:
var mappings = AutoMap.AssemblyOf<Business>()
.Where(x => x.IsSubclassOf(typeof(EntityBase)))
.IgnoreBase(typeof(EntityBase))
.Conventions.Add
(
ConventionBuilder.Id.Always(x => x.GeneratedBy.Identity()),
ConventionBuilder.HasMany.Always(x => x.Cascade.All()),
ConventionBuilder.Property.Always(x => x.Column(x.Property.Name)),
Table.Is(o => Inflector.Pluralize(o.EntityType.Name)),
PrimaryKey.Name.Is(o => "Id"),
ForeignKey.EndsWith("Id"),
DefaultLazy.Always(),
DefaultCascade.All(),
ConventionBuilder.Property.When(
c => c.Expect(x => x.Property.PropertyType.IsEnum),
x => x.CustomType(x.Property.PropertyType))
);
The last convention builder statement did the trick. I'm curious as to why Fluent NHibernate's default is to map enums as strings now. That doesn't seem to make much sense.
You should never map Enum as int in NHibernate. It becomes a reason of having a ghost updates.
The best way to it is just not setting a type property in XML mappings. To achieve that in Fluent NHibernate you can use .CustomType(string.Empty).
Some additional info you can find here.
I try to write a (fluent) mapping against an interface
public interface IOrderDiscount : IDomainObject<long>
where
public interface IDomainObject<IdT> : IDomainObject
{
IdT Id { get; }
}
like so (and all other thinkable varieties of access strategies)
Id(d => d.Id, "DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();
but all I get are variations of
Could not find field 'id' in class 'IOrderDiscount'
My base class implements this as
public virtual IdT Id { get; protected set; }
but event using a backing field does not change a thing.
So I am left to wonder, how I could get this to work...
Anyone with an idea?
Specify the custom column name via the Column method instead:
Id(d => d.Id)
.Column("DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();