Nhibernate linq fetch in subclass - c#

Help me, please, solve one problem.
I have project, which uses Nhibernate and Fluent Nhibernate. There I created one base class
(it is not real classes, but they describe my situation):
public class Document
{
public virtual int Id { get; private set; }
public virtual Account Acc { get; private set; }
}
And mapping for it:
public class DocumentMap: ProfileEntityMap<Document>
{
public DocumentMap()
{
Id(m => m.Id);
References(m => m.Acc);
DiscriminateSubClassesOnColumn("Type");
}
}
Then I implemented subclass:
public class PaymentDocument: Document
{
public virtual Card AccountCard { get; set;}
}
Mapping for class PaymentDocument:
public class PaymentDocumentMap : SubclassMap<PaymentDocument>
{
public PaymentDocumentMap()
{
References(t => t.AccountCard);
}
}
And after that I try execute this query:
payments = session.Query<PaymentDocument>()
.Fetch(t => t.Acc)
.Fetch(t => t.AccountCard)
.ToList();
And when I insert first fetch I get next exception:
Object reference not set to an instance of an object.
Can somebody answer me where is a problem?

Actually it was a bug fixed in 3.0.0.Alpha2. Right now it works with the trunk.

Related

Entity Framework navigation properties is empty after assigned Id

I have the following
public class MainClass {
public int Id { get;set; }
public virtual ICollection<SubClass> SubClasses { get;set; }
}
public class SubClass {
public int MainClassId { get;set; }
public virtual MainClass MainClass { get;set; }
}
and i have setup mapping for one to many. The problem i have is when i do this :
var subClass = new SubClass();
subClass.MainClassId = 1;
_dbset.SaveChanges(subClass);
//subClass.MainClass is null after save
i will need to call my get function with id=1 only i can get the MainClass entity. Anyone has any idea whats the issue causing this?
You should add subClass to the mainClass's collection of SubClasses and then save changes.
So like,
var mainClass = _dbset.MainClasses.Single(x => x.id == mainClassId);
var subClass = new SubClass();
//populate subClass without setting mainclassId.
mainClass.SubClasses.Add(subClass);
_dbset.SaveChanges();
The following would work:
public class MainClass {
public int Id { get;set; }
public virtual ICollection<SubClass> SubClasses { get;set; }
}
public class SubClass {
public int Id { get;set; }
[ForeignKey("MainClass")]
public int MainClassId { get;set; }
public virtual MainClass MainClass { get;set; }
}
If you used code-first approach, you might be missing modelbuilder for 1:M relationship.
In your Context in method OnModelCreating you need to have something like this.
modelBuilder.Entity<MainClass>()
.HasMany(e => e.SubClass)
.WithRequired(e => e.MainClass)
.WillCascadeOnDelete();
Next thing that comes to my mind is that you might want to use include in your get method to specify that you need to load all subclasses for main class
context.MainClass.Include(x => x.SubClass).ToList();
or use some other method to load joined data. Link
I hope that will help you.

Using a single Entity Framework Core DbContext to manage multiple database schemas with homonymous tables

In a .NET Core 2.1 library I need to access to a MySQL database organized in multiple schemas with tables that can have the same name across those schemas. I can't make any changes to the DB since it comes from another company.
For most of the tables I need a read-only access and I'd like to use a single EF Core DbContext.
Actually I get this error message during initialization:
InvalidOperationException: Cannot use table 'tbl_panel' for
entity type 'Db2Panels' since it is being used for entity
type 'Db1Panels' and there is no relationship between their
primary keys.
I think that the crux of the matter mainly resides in the configuration methods, which should be called not just once but N times, one for each instance of the entity with different schema (db_machine_1.tbl_panel, db_machine_2.tbl_panel, etc.).
How can I reach my goal?
This is my actual implementation.
Database schemas
// db_machine_1 schema
db_machine_1.tbl_panel
db_machine_1.tbl_basket
db_machine_1.tbl_unit
// db_machine_2 schema
db_machine_2.tbl_panel
db_machine_2.tbl_basket
db_machine_2.tbl_discard
// Other db_machine_X schemas with similar structure...
DbContext configuration
public class MyDbContext : DbContext
{
// Schema: db_machine_1
public DbSet<Panel> Db1Panels { get; set; }
public DbSet<Basket> Db1Baskets { get; set; }
public DbSet<Unit> Db1Units { get; set; }
// Schema: db_machine_2
public DbSet<Panel> Db2Panels { get; set; }
public DbSet<Basket> Db2Baskets { get; set; }
public DbSet<Discard> Db2Discards { get; set; }
// Other schemas DbSet<X> objects...
// Arrays to access the specific DbSet by using the schema number:
// Panels[1] -> Db1Panels, Panels[2] -> Db2Panels, ...
public DbSet<Panel>[] Panels { get; }
public DbSet<Basket>[] Baskets { get; }
// Other arrays for other DbSet<X> objects...
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
// Arrays initialization
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add(Db1Panels);
dbPanelList.Add(Db2Panels);
Panels = dbPanelList.ToArray();
List<DbSet<Basket>> dbBasketList = new List<DbSet<Basket>>();
dbBasketList.Add(Db1Baskets);
dbBasketList.Add(Db2Baskets);
Baskets = dbBasketList.ToArray();
// Initialization for other DbSet<X> objects...
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations<MyDbContext>();
modelBuilder.ApplyAllConversions();
}
}
Objects
public class Panel
{
public long Id { get; set; }
public string SN { get; set; }
// Other properties...
}
public class Basket
{
public long Id { get; set; }
public string Description { get; set; }
// Other properties...
}
Configurations
public class PanelConfiguration : IEntityTypeConfiguration<Panel>
{
public void Configure(EntityTypeBuilder<Panel> builder)
{
builder.ToTable("tbl_panel");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.SN)
.HasColumnName("Serial")
.HasMaxLength(20);
// Other properties configuration...
}
}
public class BasketConfiguration : IEntityTypeConfiguration<Basket>
{
public void Configure(EntityTypeBuilder<Basket> builder)
{
builder.ToTable("tbl_basket");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.Description)
.HasColumnName("Desc")
.HasMaxLength(100);
// Other properties configuration...
}
}
// Other IEntityTypeConfiguration implementations for other tables...
// This extension method is used to automatically load all Configurations
// of the various entities
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(T).Assembly
.GetTypes()
.Select(t => (t, i: t.GetInterfaces().FirstOrDefault(i => i.Name.Equals(typeof(IEntityTypeConfiguration<>).Name, StringComparison.Ordinal))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], cfgObj: Activator.CreateInstance(it.t)))
.Select(it => applyConfigurationMethodInfo.MakeGenericMethod(it.et).Invoke(modelBuilder, new[] { it.cfgObj }))
.ToList();
}
}
UPDATE about base class arrays
After creating base abstract classes and derived ones, I'd like to merge all the derived class objects into a single array to be able to access the specific DbSet by using the schema number. See also above code of DbContext constructor.
I'm having problems with casting...
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add((DbSet<Panel>)Db1Panels.Select(g => g as Panel)); // NOT WORKING! Cast Exception
dbPanelList.Add((DbSet<Panel>)Db2Panels.Cast<DbSet<Panel>>()); // NOT WORKING! Cast Exception
Panels = dbPanelList.ToArray();
Is this possible somehow?
I think you can't get away from having two different EF objects for the different tables, and you probably shouldn't as they may diverge at some point in the future.
At a minimum you need two classes Db1Panel and Db2Panel . I assume that actually the "Db" prefix is meant to meant a different schema, not actually a different database.
However that shouldn't be a big problem as there are other ways within C# of making them behave in similar fashions. Two options that spring to mind are having them inherit from the same base class, or have them implement an interface:
public abstract class PanelBase
{
public long Id { get; set; }
// other properties
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : PanelBase{}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : PanelBase{}
If you chose to implement the interface you would need to repeat the properties in each class, but refactoring tools make this quite easy.
public interface IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : IPanel
{
public long Id { get; set; }
}
Or depending on the size of your application you could consider having another namespace of domain objects and just map the database objects into it:
You should be able to use the Table attribute. There's a parameter Schema that allows you to set the schema name. See here for documentation. In your case you'd get something like
[Table("Table1", Schema="Schema1")]
public class Entity1Schema1
{
public string Property1 {get;set;}
}
[Table("Table1", Schema="Schema2")]
public class Entity1Schema2
{
public string Property1 {get;set;}
}
And then of course you can use interfaces or base classes to refactor your code as #ste-fu already mentioned.

Entity Framework - Mapped property will not populate, despite being retrieved by SQL

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.)

EF 4.3.1 Include inherited navigation properties in LinqToEntities query

I'm trying to setup a simple inheritance scenario with EF 4.3.1 using code first approch and fluent configuration.
I've created an abstract base type 'A' with a one-to-one navigation property and an inherited class 'AA' also with a one-to-one navigation property has following :
public abstract class A
{
public Guid ID { get; set; }
public B ChildB { get; set; }
}
public class AA : A
{
public C ChildC { get; set; }
}
public class B
{
public Guid ID { get; set; }
public A Parent { get; set; }
}
public class C
{
public Guid ID { get; set; }
public AA Parent { get; set; }
}
public class AConfiguration : EntityTypeConfiguration<A>
{
public AConfiguration()
{
this.HasRequired(o => o.ChildB)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("A");
});
}
}
public class AAConfiguration : EntityTypeConfiguration<AA>
{
public AAConfiguration()
{
this.HasRequired(o => o.ChildC)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("AA");
});
}
}
public class BConfiguration : EntityTypeConfiguration<B>
{
public BConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildB);
this.Map(o =>
{
o.ToTable("B");
});
}
}
public class CConfiguration : EntityTypeConfiguration<C>
{
public CConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildC);
this.Map(o =>
{
o.ToTable("C");
});
}
}
public class DataContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add<A>(new AConfiguration());
modelBuilder.Configurations.Add<AA>(new AAConfiguration());
modelBuilder.Configurations.Add<B>(new BConfiguration());
modelBuilder.Configurations.Add<C>(new CConfiguration());
}
public DbSet<AA> AASet { get; set; }
public DbSet<B> BSet { get; set; }
public DbSet<C> CSet { get; set; }
}
When I try to get my data back with the first level of navigation property, it works as expected :
... dataContext.AASet.Include("ChildB") ...
But when I try to include the navigation property of the inherited type like following :
... dataContext.AASet.Include("ChildC") ...
I get an EntityCommandCompilationException at runtime with the following inner exception message :
The ResultType of the specified expression is not compatible with the
required type. The expression ResultType is
'Transient.reference[...A]' but the required type is
'Transient.reference[...AA]'. Parameter name: arguments[0]
Has anybody encountered a similar issue ?
I am probably missing something but I can't see what's wrong with this sample.
What can I do to get my model works as expected ?
No, you don't miss anything. Actually you ran into an old Entity Framework bug. Your second query can be written like this:
var result = dataContext.ASet.OfType<AA>().Include("ChildC").ToList();
(when you replace your DbSet AASet by ASet).
For this type of eager loading of one-to-one mapped children on inherited types this article applies: http://weblogs.asp.net/johnkatsiotis/archive/2010/04/28/huge-ef4-inheritance-bug.aspx
The bug has been reported long time ago here: https://connect.microsoft.com/VisualStudio/feedback/details/544639/ef4-inheritance-defined-using-queryview-doesnt-work-properly-with-association
The bug still exists in EF 4.3.1. But Microsoft has announced in this thread that the bug is fixed in .NET 4.5 ( = EF 5.0).
The code would work if the relationship is one-to-many instead of one-to-one. Lazy or explicit loading would work as well (also with one-to-one relationship), I believe:
var result = dataContext.ASet.OfType<AA>().ToList();
foreach (var item in result)
dataContext.Entry(item).Reference(a => a.ChildC).Load();
But this will generate multiple queries. If you don't have performance problems with multiple queries I would prefer the last workaround - until you can migrate to EF 5.0.

Fluent NHibernate PropertyNotFoundException for Auto Property

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.

Categories