NHibernate ClassMapping creates automatic a unique key - c#

I use NHibernate with ClassMapping. I created an index on one of the properties, like this:
public class ShopMapping : ClassMapping<Shop>
{
public ShopMapping()
{
Table("Shops");
Id(p => p.Id, m => m.Generator(NHibernate.Mapping.ByCode.Generators.GuidComb));
Property(p => p.CountryCode, m =>
{
m.Length(10);
m.NotNullable(true);
m.Index("ShopCountryCodeIdx");
m.Unique(false);
});
}
}
This generates the index called ShopCountryCodeIdx, but I got also a unique index, called 'CountryCode' on the same column. I tried it with an without m.Unique(false), but no effects.
I just moved to a new pc. Old pc: VS2012 on 32bitand new pc: VS2015 on 64bit. NHiberate version is on both the same (3.4.1.4). Database is MySQL 5.7 (on old machine I used MySQL 5.5).
How is this possible?

Part of the problem found. I had in one of my other ClassMapping classes an unique index across three columns. One of the was also a CountryCode column. That results in this strange behavior.
Property(p => p.CountryCode, m =>
{
m.Length(10);
m.NotNullable(true);
m.Index("PanelShopCountryCodeIdx");
//m.UniqueKey("UK_psPanelShopnrIdx");
});
Property(p => p.ShopNr, m =>
{
m.Length(25);
m.NotNullable(true);
//m.UniqueKey("UK_psPanelShopnrIdx");
});
Property(p => p.PanelCode, m =>
{
m.Length(25);
m.NotNullable(true);
// m.UniqueKey("UK_psPanelShopnrIdx");
});
Getting rid of those m.UniqueKey statements, solved the problem (which let me with another challenge, how to create in this scenario a unique index across three columns from which one column has also an own (non unique) index...

Related

System.NullReferenceException throw when build HasForeignKey [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have code c# like this
builder.Entity<EnPpTime>()
.HasOne(a => a.EnPpTimeInMeta).WithOne(b => b.EnPpTime)
.HasForeignKey<EnPpTimeInMeta>(e => e.Id);
when run the programe throw exception
System.NullReferenceException: Object reference not set to an instance of an object.
[2021-11-11T07:06:30.667Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.ForeignKeyAttributeConvention.UpdateRelationshipBuilder(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context)
[2021-11-11T07:06:30.668Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.ForeignKeyAttributeConvention.ProcessForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext`1 context)
[2021-11-11T07:06:30.668Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder)
[2021-11-11T07:06:30.669Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnForeignKeyAddedNode.Run(ConventionDispatcher dispatcher)
[2021-11-11T07:06:30.669Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.DelayedConventionScope.Run(ConventionDispatcher dispatcher)
[2021-11-11T07:06:30.670Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run()
[2021-11-11T07:06:30.670Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run(IConventionForeignKey foreignKey)
[2021-11-11T07:06:30.671Z] at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionBatchExtensions.Run(IConventionBatch batch, InternalForeignKeyBuilder relationshipBuilder)
[2021-11-11T07:06:30.671Z] at Microsoft.EntityFrameworkCore.Metadata.Builders.ReferenceReferenceBuilder.HasForeignKeyBuilder(EntityType dependentEntityType, String dependentEntityTypeName, Func`3 hasForeignKey)
[2021-11-11T07:06:30.672Z] at Microsoft.EntityFrameworkCore.Metadata.Builders.ReferenceReferenceBuilder.HasForeignKeyBuilder(EntityType dependentEntityType, String dependentEntityTypeName, IReadOnlyList`1 foreignKeyMembers)
[2021-11-11T07:06:30.672Z] at Microsoft.EntityFrameworkCore.Metadata.Builders.ReferenceReferenceBuilder`2.HasForeignKey[TDependentEntity](Expression`1 foreignKeyExpression)
How to fix this exception
Looks like You're missing the
.HasPrincipalKey
in this case it will default to look for entityname suffixed "ID" in Your case "EnPpTimeID" which if it doesn't exist will fail
It is a very good idea when using entity framework always to use EntityTypeNameID for identity column in this case you will not have to explicitly defined the foreign and principal key
This appears to be a known bug when there are multiple one-to-one relationships between two entity types.
Calling entity.HasOne(d => d.Parent).WithOne(p => p.Child) before entity.HasKey() solved the problem for me. Read on for details:
I had the same problem using EF core 5 - .HasForeignKey() throws the said exception.
The DbContext was scaffolded. Scaffoling does not generate .HasPrincipalKey() for single-column foreign keys. Manually adding .HasPrincipalKey() did not fix the problem. My tables however had two 1:1 relationships. This means that the child table has 4 constraints: two foreign keys and two unique keys on the same columns as the FKs. The scaffolded code is:
builder.Entity<Child>(entity =>
{
// 2 unique keys.
entity.HasKey(e => e.ParentId);
entity.HasIndex(e => new { e.OtherId, e.ParentId }).IsUnique();
entity.Property(e => e.ParentId).ValueGeneratedNever();
// 2 foreign keys.
entity.HasOne(d => d.Parent).WithOne(p => p.Child)
.HasForeignKey<Child>(d => d.ParentId); // <---- Throws exception!
entity.HasOne(d => d.Parent2).WithOne(p => p.Child2)
.HasPrincipalKey<Parent>(p => new { p.OtherId, p.Id })
.HasForeignKey<Child>(d => new { d.OtherId, d.ParentId });
});
When I moved entity.HasOne(d => d.Parent).WithOne(p => p.Child)... before entity.HasKey() the problem disappeared. However, you should not modify generated code. I inherited the scaffolded DbContext and in the subclass wrote this:
protected override void OnModelCreating(ModelBuilder builder)
{
// Bugfix:
builder.Entity<Child>().HasOne(d => d.Parent).WithOne(p => p.Child);
// You don't even need .HasForeignKey() here. It will be called inside base.OnModelCreating(). Duplicating .HasOne() and .WithOne() works fine.
base.OnModelCreating(builder);
}

Using RefactorThis.GraphDiff library

I have the following tables in my database
And a graph mapping of (table address not shown in image):
.OwnedEntity(s => s.Address)
.OwnedCollection(s => s.SiteActivityPlants)
.OwnedCollection(s => s.SiteActivityPlants, with => with.AssociatedCollection(d => d.SiteActivityPlantNotes))
What I am trying to do is update the data in table SiteActivityPlantNotes.
After looking at Documentation, AssociatedCollection just updates the reference, what I need to do is update the data records within SiteActivityPlantNotes.
Is this possible?
2 Changes in your code should do it:
Removed second line ".OwnedCollection(s => s.SiteActivityPlants)"
as it is already part of the third line
Changed in third line "with.AssociatedCollection" to
"with.OwnedCollection"
Final Code:
.OwnedEntity(s => s.Address)
.OwnedCollection(s => s.SiteActivityPlants, with => with.OwnedCollection(d => d.SiteActivityPlantNotes))

Select a specific column with multiple includes in Entity Framework

I'm trying to get specific columns from a context with several includes, but when I try:
Context.Include(i => i.c)
.Include(i => i.l).Select(s=> new LocationCatalog { Name = s.Name})
.Include(i => i.p)
.Include(i => i.li)
.Include(i => i.pcl)
VS throws an error after the select.
How can I achieve this? I want to specify the columns for each include.
The error says that for example: i.p doesn't contains definition for i.l
That's not possible with Entity Framework. You either include the entire table (with Include, as you are doing) or you don't include it at all.
If you want to load only specific columns, you can do this, but see how it's a manual process:
Context
.Select(i => new YourType
{
c = i.l,
l = i.l,
x = new X
{
a = i.x.a // only the properties you want here
}
...
});
The moment you use Select, Include is completely ignored, so you cannot use both.

Filter nested models on property in last node with NHibernate

I am using NHibernate with mapping by code.
I have three models: Solution, Installation and System. There are one-to-many relations between them. So that each Solution has a list of Installations, and each Installation has a list of Systems.
Each system has a property "Type", which can be "1" or "0".
I am trying to write a method in the Solution repository that will return all the Solutions, with their Installations with only the Systems of type "1".
I have tried the Where-keyword in the SystemMap but i get the same result with and without it. Then i tried a few different experiments with QueryOver(???) without success.
How do i go about to filter on information in the last node?
Thank to your answer, i have done the following implementation, but it results in a huge amount of Systems and Solutions. Maybe i have done something wrong?
The Maps are as follows:
public SAPSolutionMap()
{
Id(t => t.YPID);
Property(e => e.ShortName);
Property(e => e.FullName);
Bag(x => x.SapInstallations, colmap =>
{
colmap.Table("SAPInstallation");
colmap.Key(x => x.Column("Solution"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
colmap.Cascade(Cascade.None);
}, map => map.OneToMany(m => m.Class(typeof(SAPInstallation))));
}
public SAPInstallationMap()
{
Id(t => t.InstallationNumber);
Bag(x => x.SapSystems, colmap =>
{
colmap.Table("sapgui");
colmap.Key(x => x.Column("Installation"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Cascade(Cascade.None);
colmap.Fetch(CollectionFetchMode.Join);
//colmap.Where("Type = 1");
}, map => map.OneToMany(m => m.Class(typeof(SAPSystem))));
ManyToOne(x => x.SapSolution, map =>
{
map.Column("Solution");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPSolution));
});
}
public SAPSystemMap()
{
Id(t => t.ID, t => t.Generator(Generators.Identity));
Property(e => e.Type);
Property(e => e.ExplanationText);
ManyToOne(x => x.SapInstallation, map =>
{
map.Column("Installation");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPInstallation));
});
}
And the Query:
public IList<SAPSolution> GetProductionSystems()
{
SAPSystem syst = null;
SAPInstallation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.SapInstallation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.SapSolution.YPID);
// main Query
var query = Session.QueryOver<SAPSolution>()
.WithSubquery
.WhereProperty(root => root.YPID)
.In(subquery);
return query.List<SAPSolution>();
}
Thank you!
General solution should be:
// this is a subquery (SELECT ....
System syst = null;
Installation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.Installation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.Solution.ID)
;
// main Query
var query = session.QueryOver<Solution>()
.WithSubquery
.WhereProperty(root => root.ID)
.In(subquery)
;
var list = query
.Take(10)
.Skip(10)
.List<Solution>();
What we can see, that Solution, Installation and System
System has property Installation (many-to-one)
Installation has property Solution (many-to-one)
This is expect-able, because it goes side by side with one-to-many (it is the reverse mapping)
So, then we create subquery, which returns just solution ID's which belong to system with searched Type.
Main query is flat (the great benefit) and we can use paging on top of it.
We would be able to do that even if there is only one way (one-to-many). But that will generate more complicated SQL query ... and does not make sense. In C# we can have both relations...
EXTEND:
You did a great job. Your mapping and query is really cool. But there is one big but: LAZY is what we should/MUST use. Check this:
NHibernate is lazy, just live with it, by Ayende
So, our, collections cannot be FETCHING with a JOIN, because that will multiply the result (10 solutions * 100 installation * 10 systems == 10000 results)
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
We should use LAZY as possible. To avoid later 1 + N issue, we can use batch-fetching (for example check this)
How to Eager Load Associations without duplication in NHibernate?
So, our collections should be mapped like this:
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.Lazy);
colmap.BatchSize(100);
With this setting, the query will really use only the root object and related collections will be loaded very effectively

Why aren't my included entities actually included?

I'm using Entity Framework 4 in Visual Studio 2010, with C#.
I have a method used in a repository that returns a object set with various navigation properties included. Up until recently, this method looked like this...
private IEnumerable<VRTSystem> GetSystems() {
return ctx
.Include(s => s.Customer.CustomerType)
.Include(s => s.VRTSystemProductConfigurations);
}
...where ctx is an ObjectSet of generic type VRTSystem. The full method had a lot more .Include()s than this, but this is enough to show the point.
This worked fine, however I needed to add some code to ensure that only VRTSystemProductConfigurations that had the Active flag set to true were returned. Following the advice commonly given for such situations, I changed the code to look like this...
private IEnumerable<VRTSystem> GetSystems() {
return ctx
.Include(s => s.Customer.CustomerType)
.Include(s => s.VRTSystemProductConfigurations)
.Select(s => new {
System = s,
VRTSystemProductConfigurations = s.VRTSystemProductConfigurations.Where(pc => pc.Active)
})
.Select(s => s.System);
}
However, this new version does not include any of the navigation properties, they are all null.
Anyone any idea why?
This is because Entity Framework is not entirely stupid. It sees that in the end only Systems are queried, so it cuts everything in between and returns Systems only. And part of the trick you're executing here is to disable lazy loading, so the navigation properties are null and will remain null.
You have to remove the last Select out of the scope of the EF query provider by adding an AsEnumerable:
return ctx
.Include(s => s.Customer.CustomerType)
.Select(s => new {
System = s,
VRTSystemProductConfigurations = s.VRTSystemProductConfigurations.Where(pc => pc.Active)
})
.AsEnumerable()
.Select(s => s.System);
And you don't want to include VRTSystemProductConfigurations, because that's the collection you want to load partly.

Categories

Resources