Fluent nHibernate - trying to cache a select statement - c#

I am trying to cache a select statement with no success.
I have this entity
public class Merchant : Entity<Merchant>
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Email { get; set; }
public virtual string OrdersEmail { get; set; }
public virtual string Host { get; set; }
}
The mapping is like this
public class MerchantMap : ClassMap<Merchant>
{
public MerchantMap()
{
Id(m => m.Id);
Map(m => m.Name);
Map(m => m.OrdersEmail);
Map(m => m.Email);
Map(m => m.Host);
Cache.ReadWrite();
ReadOnly();
}
}
.
The session factory is produced with this class:
public ISessionFactory TenantConfiguration()
{
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings["Tenant"]
.ConnectionString;
var config = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString)
)
.Mappings(cfg => cfg.FluentMappings
.AddFromAssemblyOf<Entities.Merchant>()
)
.Cache(c =>
c.UseQueryCache()
.UseSecondLevelCache()
.ProviderClass<NHibernate.Cache.HashtableCacheProvider>()
.RegionPrefix("Tenant")
)
.ExposeConfiguration(c => c.SetProperty(
NHibernate.Cfg
.Environment
.SessionFactoryName,
"Tenant")
)
.BuildConfiguration();
var factory = config.BuildSessionFactory();
return factory;
}
public ISession CreateMerchantSession()
{
lock (factorySyncRoot)
{
return TenantConfiguration().OpenSession();
}
}
The only query i want to cache is the following one:
var merchants = new TenantSessionSource().CreateMerchantSession()
.QueryOver<Merchant>()
.Cacheable().Future().ToList();
I have tried almost everything and have red all the related questions but with no success. From the nhibernate profiler i see that everytime it opens a new session and executes this query repeatedly.
Any ideas?
Thanks

Related

.NET Core API - Unknown Column in field list

I'm new to C# / .NET Core and I'm trying to create a Web API. I have 2 models; Community & Rank
Community:
public class Community
{
public long Id { get; set; }
public long FrontId { get; set; }
public string Name { get; set; }
[ForeignKey("CommunityId")]
public virtual ICollection<Rank> Ranks { get; set; }
}
Rank:
public class Rank
{
public long Id { get; set; }
public long CommunityId { get; set; } [Required]
public string Name { get; set; } [Required]
public string Prefix { get; set; }
public virtual Community Community { get; set; }
}
I have my context set up like so:
public class CommunityContext : DbContext
{
public DbSet<Community> Communities { get; set; }
public DbSet<Rank> Ranks { get; set; }
public CommunityContext(DbContextOptions<CommunityContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//Communities
builder.Entity<Community>(entity =>
{
entity.HasKey(c => c.Id);
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
entity.HasKey(r => r.Id);
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
}
}
I've done my database migrations etc, and when creating new entries, all works as expected currently, however trying to GET either communities or ranks returns the error:
MySql.Data.MySqlClient.MySqlException (0x80004005): Unknown column 'r.CommunityId1' in 'field list'
Any suggestions would be appreciated!
Thanks
Your Configurations should be like this:
...
//Communities
builder.Entity<Community>(entity =>
{
builder.ToTable("Community");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("CommunityId").ValueGeneratedOnAdd();
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
builder.ToTable("Rank");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).HasColumnName("RankId").ValueGeneratedOnAdd();
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
...

Fluent NHibernate: ISet of base class

In my project I have a base class (not mapped):
public abstract class BaseEntity
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Also I have a few inherited classes (they look all almost the same, so here is a code and map for only one)
public class User : BaseEntity
{
public virtual int UserId { get; set; }
public virtual string Login { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Entities { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
this.Id(x => x.UserId);
this.Map(x => x.Login);
this.Map(x => x.PasswordHash);
this.HasManyToMany<BaseEntity>(x => x.Entities);
}
}
Next, I have a NHibernateHelper:
public class NHibernateHelper
{
public static ISession OpenSession()
{
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(#"someconstring")
.ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<User>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
return sessionFactory.OpenSession();
}
}
And here is a question:
How can I exclude BaseEntity class from mapping, if I need table like EnitiyToEntity in my Database for many-to-many relationship?
Take a look to this:
https://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat
If I understand your question the solution should be to implement TPC (Table per concrete class).
By the way, in your mapping you have to use the concrete type for HasManyToMany.
For example (I supposed your user is referenced to many groups):
HasManyToMany<Group>(x => x.Entities).Table("UsersGroups");
where the Group class is something like this:
public class Group : BaseEntity
{
public virtual int GroupId { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Members { get; set; }
}
And in the GroupMap class you can reference the users like this:
HasManyToMany<User>(x => x.Members).Table("UsersGroups");
If you reference a class you have to map it. So map Entity as ClassMap and all the others as SubclassMap. They will end up as union subclass which is one table per class. Unfortunatly you can not map a hasmanytoany with FNH.
You can map it as hasmanytomany and work around it:
var config = new Configuration();
config.BeforeBindMapping += BeforeBindMapping;
_config = Fluently
.Configure(config)
...
private void BeforeBindMapping(object sender, NHCfg.BindMappingEventArgs e)
{
var userclass = e.Mapping.RootClasses.FirstOrDefault(rc => rc.name.StartsWith(typeof(User).FullName));
if (userclass != null)
{
HbmSet prop = (HbmSet)paymentclass.Properties.FirstOrDefault(rc => rc.Name == "Entities");
prop.Item = new HbmManyToAny // == prop.ElementRelationship
{
column = new[]
{
new HbmColumn { name = "entityType", notnull = true, notnullSpecified = true },
new HbmColumn { name = "entity_id", notnull = true, notnullSpecified = true }
},
idtype = "Int64",
metatype = "String",
metavalue = typeof(Entity).Assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract && typeof(Entity).IsAssignableFrom(t))
.Select(t => new HbmMetaValue { #class = t.AssemblyQualifiedName, value = t.Name })
.ToArray()
};
}
}

Repositories in .net core library

I'm trying to wrap basic actions fro model(like get by id, etc.) in repositories. However I'm facing with two problems:
1. Connection String : Is there any solution to put it in config, and not to hard code it in class, like I did?
public const string ConnectionString = "Server = (localdb)\\mssqllocaldb;Database=pinchdb;Trusted_Connection=True;MultipleActiveResultSets=true";// can I get rid of it?
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectDepartments> ProjectsDepartments { get; set; }
public DbSet<Roles> Roles { get; set; }
public effMercContext(DbContextOptions<effMercContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProjectDepartments>()
.HasKey(t => new { t.ProjectId, t.DepartmentId });
modelBuilder.Entity<ProjectDepartments>()
.HasOne(pt => pt.Project)
.WithMany(p => p.ProjectDepartment)
.HasForeignKey(pt => pt.ProjectId);
modelBuilder.Entity<ProjectDepartments>()
.HasOne(pt => pt.Department)
.WithMany(t => t.ProjectDepartment)
.HasForeignKey(pt => pt.DepartmentId);
}
}
public class EffMercDbContextFactory : IDbContextFactory<effMercContext>
{
public effMercContext Create(DbContextFactoryOptions options)
{
var builder = new DbContextOptionsBuilder<effMercContext>();
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=pinchdb;Trusted_Connection=True;MultipleActiveResultSets=true");
return new effMercContext(builder.Options);
}
}
2. Can I call Dbcontext without options to implement methods?
public Employee GetByID(int id)
{
var optionsBuilder = new DbContextOptionsBuilder<effMercContext>();// how can I get rid of this and just call effMercContext
optionsBuilder.UseSqlServer(effMercContext.ConnectionString);
using (effMercContext db = new effMercContext(optionsBuilder.Options))
{
return db.Employees.Where(x => x.Id == id).FirstOrDefault();
}
}
You can put your connectionstring in appsettings.json as shown below.
Also, you can have config transforms for the appsettings.json based on the environments.
in the Startup.cs
Access the above config while creating the DbContext.
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseSqlServer(
configuration["Data:DefaultConnection:ConnectionString"], b =>
b.MigrationsAssembly("MyProj.Web"))
Add a default constructor to effMercContext. Then you shouldn't need to pass DbContextOptions.

EF code first FLUENT: many to many with additional field in jointable

I'm building a wiki, which has articles that consists of subarticles. An article can consist of multiple subarticles, and a subarticle can be attached to multiple articles. In the jointable there is a sortorder that defines the display of subarticles for a particular article.
The parent-article only consists of a title (and metadata), no text, all text is done through subarticles.
This sortorder in the jointable though, is where I'm stuck atm, I can't access it from my query. Hopefully someone can point me in the right direction.
Sidenote: I'm quite new in the whole MVC/EF world, even c#/vb and .NET is something I've only been working on since a few months and in my spare time.
I have these classes:
Article:
public class Article : BaseEntity
{
private ICollection<Category> _categories;
private ICollection<ArticleSubarticle> _subarticles;
public string Title { get; set; }
public int AuthorId { get; set; }
public DateTime CreationDate { get; set; }
public DateTime ?PublishDate { get; set; }
public DateTime ?ChangeDate { get; set; }
public bool Published { get; set; }
public virtual ICollection<Category> Categories
{
get { return _categories ?? (_categories = new List<Category>()); }
protected set { _categories = value; }
}
public virtual ICollection<ArticleSubarticle> Subarticles
{
get { return _subarticles ?? (_subarticles = new List<ArticleSubarticle>()); }
protected set { _subarticles = value; }
}
}
Subarticle
public class Subarticle : Article
{
private ICollection<Attachment> _attachments;
public string ArticleText { get; set; }
public int OriginalArticle { get; set; }
public bool Active { get; set; }
public virtual ICollection<Attachment> Attachments
{
get { return _attachments ?? (_attachments = new List<Attachment>()); }
protected set { _attachments = value; }
}
}
Jointable:
public class ArticleSubarticle : BaseEntity
{
public int ParentId { get; set; }
public int ChildId { get; set; }
public int SortOrder { get; set; }
public virtual Article Parent { get; set; }
public virtual Subarticle Child { get; set; }
}
They are mapped as follows:
Article
public ArticleMap () {
ToTable("Wiki_Article");
HasKey(a => a.Id);
Property(a => a.Title).HasColumnType("VARCHAR").HasMaxLength(250);
Property(a => a.AuthorId);
Property(a => a.PublishDate).IsOptional();
Property(a => a.ChangeDate).IsOptional();
HasMany(a => a.Categories)
.WithMany()
.Map(a => a.ToTable("Wiki_Article_Category_Mapping"));
}
Subarticle
public SubarticleMap()
{
ToTable("Wiki_Subarticle");
HasKey(sa => sa.Id);
Property(a => a.ArticleText)
.IsOptional()
.HasColumnType("TEXT");
Property(a => a.OriginalArticle)
.IsOptional();
HasMany(a => a.Attachments)
.WithMany()
.Map(a => a.ToTable("Wiki_Subarticle_Attachment_Mapping"));
}
Jointable
public ArticleSubarticleMap()
{
ToTable("Wiki_Article_Subarticle_Mapping");
HasKey(asa => new { asa.ParentId, asa.ChildId });
HasRequired(asa => asa.Parent)
.WithMany(asa => asa.Subarticles)
.HasForeignKey(asa => asa.ParentId);
}
This gets me the database as expected.
Now I want an article with its subarticles, that are ordered by the sortorder.
This query gets me the article with its subarticles, but I can't seem to figure out how to reach this sortorder in the Wiki_Article_Subarticle_Mapping table.
public IList<Article> getArticleByIdWithSortedSubarticles(int ArticleId)
{
var query = _articleRepository.Table;
query = query.Where(a => ArticleId == a.Id)
.Select(a => a);
var subarticles = query.ToList();
return subarticles;
}
Any ideas?
Thanks in advance!
Your query is not loading subarticles currently, so I guess they are lazily loaded. Try loading them explicitly like this:
public IList<Article> getArticleByIdWithSortedSubarticles(int ArticleId)
{
var query = _articleRepository.Table;
query = query.Where(a => ArticleId == a.Id)
.Select(a => new { article = a, subs = a.SubArticles.OrderBy(s => s.SortOrder) });
return query.AsEnumerable().Select(m => m.article).ToList();
}

Fluent NHibernate child class mapping using DiscriminateSubClassesOnColumn issue

I have an issue with mapping, simplified my relationship looks like this.
I have parent class:
public abstract class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
and two subclasses:
public class UploadedFileDocument : DocumentType
{
}
public class ApplicationFormDocument : DocumentType
{
}
mapped like this:
public DocumentTypeMap()
{
Schema("Core");
Id(x => x.Id);
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
public class UploadedFileDocumentMap : SubclassMap<UploadedFileDocument>
{
}
public class ApplicationFormDocumentMap : SubclassMap<ApplicationFormDocument>
{
}
Then I have another entity with a FK to DocumentType, mapped like this:
public FileConversionMap()
{
Schema("Core");
Id(x => x.Id);
References(x => x.Application).Not.Nullable();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
my issue is, when I retrieve rows from the DB like this:
Session.Query<FileConversion>().AsQueryable();
all the rows come back with the DocumentType being of type DocumentType, not of the child type (ie the actual type of that property, ie. when i do .GetType(), either UploadedFileDocument or ApplicationFormDocument)
Apologies if this is just me being dim. But how can I determine which type of DocumentType I have ... is my mapping wrong?
When you look at your generated SQL (adding .ShowSQL() to your .Database method), do you see the Type being entered? You should see something similar to:
INSERT
INTO
"Core_DocumentType"
(Name, Type)
VALUES
(#p0, 'ApplicationFormDocument');
select
last_insert_rowid();
#p0 = 'afd' [Type: String (0)]
Using the mappings you provided, it looks fine and I could return the DocumentType (using SQLite) just fine.
Here's the code I used to reproduce it. I didn't have your FileConversion object, so please verify that it matches what you need.
DocumentType
public class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class DocumentTypeMap : ClassMap<DocumentType>
{
public DocumentTypeMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
}
UploadFileDocument/ApplicationFormDocument
public class UploadedFileDocument : DocumentType
{
public virtual string ContentType { get; set; }
}
public class ApplicationFormDocument : DocumentType
{
}
public class UploadFileDocumentMap :
SubclassMap<UploadedFileDocument>
{
public UploadFileDocumentMap()
{
GenerateMap();
}
void GenerateMap()
{
Map(x => x.ContentType);
}
}
public class ApplicationFormDocumentMap :
SubclassMap<ApplicationFormDocument>
{
}
FileConversion
public class FileConversion
{
public virtual int Id { get; set; }
public virtual DocumentType DocumentType { get; set; }
}
public class FileConversionMap : ClassMap<FileConversion>
{
public FileConversionMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
}
The tests I used (using machine.specifications):
Context
public class when_discriminating_on_subclass
{
static IList<FileConversion> results;
Establish context = () =>
{
using (var session = DataConfiguration.CreateSession())
{
using (var transaction = session.BeginTransaction())
{
var upload = new UploadedFileDocument
{ Name = "uploaded", ContentType = "test" };
var form = new ApplicationFormDocument
{ Name = "afd" };
session.Save(form);
session.Save(upload);
var formConversion =
new FileConversion { DocumentType = form };
var uploadConversion =
new FileConversion { DocumentType = upload };
session.Save(formConversion);
session.Save(uploadConversion);
transaction.Commit();
}
using (var transaction = session.BeginTransaction())
{
results = session.Query<FileConversion>().AsQueryable().ToList();
transaction.Commit();
}
}
};
Specifications
It should_return_two_results = () =>
results.Count.ShouldEqual(2);
It should_contain_one_of_type_uploaded_file = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(UploadedFileDocument))
.ShouldEqual(1);
It should_contain_one_of_type_application_form = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(ApplicationFormDocument))
.ShouldEqual(1);
}
Debugging through the assertions, I can see that the collection comes back with the two types:
Are you casting them back to the base type anywhere in your mappings or classes?

Categories